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, week_fmt}
96 {6 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 1, TMT_MONTH, 1, 30 * 24 * 3600,
99 {48 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 3, TMT_MONTH, 3, 30 * 24 * 3600,
102 {315360, 0, TMT_MONTH, 3, TMT_YEAR, 1, TMT_YEAR, 1, 365 * 24 * 3600, "%Y"}
104 {10 * 24 * 3600, 0, TMT_YEAR, 1, TMT_YEAR, 1, TMT_YEAR, 1,
105 365 * 24 * 3600, "%y"}
107 {-1, 0, TMT_MONTH, 0, TMT_MONTH, 0, TMT_MONTH, 0, 0, ""}
110 /* sensible y label intervals ...*/
134 {20.0, {1, 5, 10, 20}
140 {100.0, {1, 2, 5, 10}
143 {200.0, {1, 5, 10, 20}
146 {500.0, {1, 2, 4, 10}
154 gfx_color_t graph_col[] = /* default colors */
156 {1.00, 1.00, 1.00, 1.00}, /* canvas */
157 {0.95, 0.95, 0.95, 1.00}, /* background */
158 {0.81, 0.81, 0.81, 1.00}, /* shade A */
159 {0.62, 0.62, 0.62, 1.00}, /* shade B */
160 {0.56, 0.56, 0.56, 0.75}, /* grid */
161 {0.87, 0.31, 0.31, 0.60}, /* major grid */
162 {0.00, 0.00, 0.00, 1.00}, /* font */
163 {0.50, 0.12, 0.12, 1.00}, /* arrow */
164 {0.12, 0.12, 0.12, 1.00}, /* axis */
165 {0.00, 0.00, 0.00, 1.00} /* frame */
172 # define DPRINT(x) (void)(printf x, printf("\n"))
178 /* initialize with xtr(im,0); */
186 pixie = (double) im->xsize / (double) (im->end - im->start);
189 return (int) ((double) im->xorigin + pixie * (mytime - im->start));
192 /* translate data values into y coordinates */
201 if (!im->logarithmic)
202 pixie = (double) im->ysize / (im->maxval - im->minval);
205 (double) im->ysize / (log10(im->maxval) - log10(im->minval));
207 } else if (!im->logarithmic) {
208 yval = im->yorigin - pixie * (value - im->minval);
210 if (value < im->minval) {
213 yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
221 /* conversion function for symbolic entry names */
224 #define conv_if(VV,VVV) \
225 if (strcmp(#VV, string) == 0) return VVV ;
231 conv_if(PRINT, GF_PRINT);
232 conv_if(GPRINT, GF_GPRINT);
233 conv_if(COMMENT, GF_COMMENT);
234 conv_if(HRULE, GF_HRULE);
235 conv_if(VRULE, GF_VRULE);
236 conv_if(LINE, GF_LINE);
237 conv_if(AREA, GF_AREA);
238 conv_if(GRAD, GF_GRAD);
239 conv_if(STACK, GF_STACK);
240 conv_if(TICK, GF_TICK);
241 conv_if(TEXTALIGN, GF_TEXTALIGN);
242 conv_if(DEF, GF_DEF);
243 conv_if(CDEF, GF_CDEF);
244 conv_if(VDEF, GF_VDEF);
245 conv_if(XPORT, GF_XPORT);
246 conv_if(SHIFT, GF_SHIFT);
248 return (enum gf_en)(-1);
251 enum gfx_if_en if_conv(
255 conv_if(PNG, IF_PNG);
256 conv_if(SVG, IF_SVG);
257 conv_if(EPS, IF_EPS);
258 conv_if(PDF, IF_PDF);
260 return (enum gfx_if_en)(-1);
263 enum tmt_en tmt_conv(
267 conv_if(SECOND, TMT_SECOND);
268 conv_if(MINUTE, TMT_MINUTE);
269 conv_if(HOUR, TMT_HOUR);
270 conv_if(DAY, TMT_DAY);
271 conv_if(WEEK, TMT_WEEK);
272 conv_if(MONTH, TMT_MONTH);
273 conv_if(YEAR, TMT_YEAR);
274 return (enum tmt_en)(-1);
277 enum grc_en grc_conv(
281 conv_if(BACK, GRC_BACK);
282 conv_if(CANVAS, GRC_CANVAS);
283 conv_if(SHADEA, GRC_SHADEA);
284 conv_if(SHADEB, GRC_SHADEB);
285 conv_if(GRID, GRC_GRID);
286 conv_if(MGRID, GRC_MGRID);
287 conv_if(FONT, GRC_FONT);
288 conv_if(ARROW, GRC_ARROW);
289 conv_if(AXIS, GRC_AXIS);
290 conv_if(FRAME, GRC_FRAME);
292 return (enum grc_en)(-1);
295 enum text_prop_en text_prop_conv(
299 conv_if(DEFAULT, TEXT_PROP_DEFAULT);
300 conv_if(TITLE, TEXT_PROP_TITLE);
301 conv_if(AXIS, TEXT_PROP_AXIS);
302 conv_if(UNIT, TEXT_PROP_UNIT);
303 conv_if(LEGEND, TEXT_PROP_LEGEND);
304 conv_if(WATERMARK, TEXT_PROP_WATERMARK);
305 return (enum text_prop_en)(-1);
315 cairo_status_t status = (cairo_status_t) 0;
320 if (im->daemon_addr != NULL)
321 free(im->daemon_addr);
323 for (i = 0; i < (unsigned) im->gdes_c; i++) {
324 if (im->gdes[i].data_first) {
325 /* careful here, because a single pointer can occur several times */
326 free(im->gdes[i].data);
327 if (im->gdes[i].ds_namv) {
328 for (ii = 0; ii < im->gdes[i].ds_cnt; ii++)
329 free(im->gdes[i].ds_namv[ii]);
330 free(im->gdes[i].ds_namv);
333 /* free allocated memory used for dashed lines */
334 if (im->gdes[i].p_dashes != NULL)
335 free(im->gdes[i].p_dashes);
337 free(im->gdes[i].p_data);
338 free(im->gdes[i].rpnp);
342 for (i = 0; i < DIM(text_prop);i++){
343 pango_font_description_free(im->text_prop[i].font_desc);
344 im->text_prop[i].font_desc = NULL;
347 if (im->font_options)
348 cairo_font_options_destroy(im->font_options);
351 status = cairo_status(im->cr);
352 cairo_destroy(im->cr);
356 if (im->rendered_image) {
357 free(im->rendered_image);
361 g_object_unref (im->layout);
365 cairo_surface_destroy(im->surface);
368 fprintf(stderr, "OOPS: Cairo has issues it can't even die: %s\n",
369 cairo_status_to_string(status));
374 /* find SI magnitude symbol for the given number*/
376 image_desc_t *im, /* image description */
382 char *symbol[] = { "a", /* 10e-18 Atto */
383 "f", /* 10e-15 Femto */
384 "p", /* 10e-12 Pico */
385 "n", /* 10e-9 Nano */
386 "u", /* 10e-6 Micro */
387 "m", /* 10e-3 Milli */
392 "T", /* 10e12 Tera */
393 "P", /* 10e15 Peta */
400 if (*value == 0.0 || isnan(*value)) {
404 sindex = floor(log(fabs(*value)) / log((double) im->base));
405 *magfact = pow((double) im->base, (double) sindex);
406 (*value) /= (*magfact);
408 if (sindex <= symbcenter && sindex >= -symbcenter) {
409 (*symb_ptr) = symbol[sindex + symbcenter];
416 static char si_symbol[] = {
417 'a', /* 10e-18 Atto */
418 'f', /* 10e-15 Femto */
419 'p', /* 10e-12 Pico */
420 'n', /* 10e-9 Nano */
421 'u', /* 10e-6 Micro */
422 'm', /* 10e-3 Milli */
427 'T', /* 10e12 Tera */
428 'P', /* 10e15 Peta */
431 static const int si_symbcenter = 6;
433 /* find SI magnitude symbol for the numbers on the y-axis*/
435 image_desc_t *im /* image description */
439 double digits, viewdigits = 0;
442 floor(log(max(fabs(im->minval), fabs(im->maxval))) /
443 log((double) im->base));
445 if (im->unitsexponent != 9999) {
446 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
447 viewdigits = floor((double)(im->unitsexponent / 3));
452 im->magfact = pow((double) im->base, digits);
455 printf("digits %6.3f im->magfact %6.3f\n", digits, im->magfact);
458 im->viewfactor = im->magfact / pow((double) im->base, viewdigits);
460 if (((viewdigits + si_symbcenter) < sizeof(si_symbol)) &&
461 ((viewdigits + si_symbcenter) >= 0))
462 im->symbol = si_symbol[(int) viewdigits + si_symbcenter];
467 /* move min and max values around to become sensible */
472 double sensiblevalues[] = { 1000.0, 900.0, 800.0, 750.0, 700.0,
473 600.0, 500.0, 400.0, 300.0, 250.0,
474 200.0, 125.0, 100.0, 90.0, 80.0,
475 75.0, 70.0, 60.0, 50.0, 40.0, 30.0,
476 25.0, 20.0, 10.0, 9.0, 8.0,
477 7.0, 6.0, 5.0, 4.0, 3.5, 3.0,
478 2.5, 2.0, 1.8, 1.5, 1.2, 1.0,
479 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0, -1
482 double scaled_min, scaled_max;
489 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
490 im->minval, im->maxval, im->magfact);
493 if (isnan(im->ygridstep)) {
494 if (im->extra_flags & ALTAUTOSCALE) {
495 /* measure the amplitude of the function. Make sure that
496 graph boundaries are slightly higher then max/min vals
497 so we can see amplitude on the graph */
500 delt = im->maxval - im->minval;
502 fact = 2.0 * pow(10.0,
504 (max(fabs(im->minval), fabs(im->maxval)) /
507 adj = (fact - delt) * 0.55;
510 ("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n",
511 im->minval, im->maxval, delt, fact, adj);
516 } else if (im->extra_flags & ALTAUTOSCALE_MIN) {
517 /* measure the amplitude of the function. Make sure that
518 graph boundaries are slightly lower than min vals
519 so we can see amplitude on the graph */
520 adj = (im->maxval - im->minval) * 0.1;
522 } else if (im->extra_flags & ALTAUTOSCALE_MAX) {
523 /* measure the amplitude of the function. Make sure that
524 graph boundaries are slightly higher than max vals
525 so we can see amplitude on the graph */
526 adj = (im->maxval - im->minval) * 0.1;
529 scaled_min = im->minval / im->magfact;
530 scaled_max = im->maxval / im->magfact;
532 for (i = 1; sensiblevalues[i] > 0; i++) {
533 if (sensiblevalues[i - 1] >= scaled_min &&
534 sensiblevalues[i] <= scaled_min)
535 im->minval = sensiblevalues[i] * (im->magfact);
537 if (-sensiblevalues[i - 1] <= scaled_min &&
538 -sensiblevalues[i] >= scaled_min)
539 im->minval = -sensiblevalues[i - 1] * (im->magfact);
541 if (sensiblevalues[i - 1] >= scaled_max &&
542 sensiblevalues[i] <= scaled_max)
543 im->maxval = sensiblevalues[i - 1] * (im->magfact);
545 if (-sensiblevalues[i - 1] <= scaled_max &&
546 -sensiblevalues[i] >= scaled_max)
547 im->maxval = -sensiblevalues[i] * (im->magfact);
551 /* adjust min and max to the grid definition if there is one */
552 im->minval = (double) im->ylabfact * im->ygridstep *
553 floor(im->minval / ((double) im->ylabfact * im->ygridstep));
554 im->maxval = (double) im->ylabfact * im->ygridstep *
555 ceil(im->maxval / ((double) im->ylabfact * im->ygridstep));
559 fprintf(stderr, "SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
560 im->minval, im->maxval, im->magfact);
568 if (isnan(im->minval) || isnan(im->maxval))
571 if (im->logarithmic) {
572 double ya, yb, ypix, ypixfrac;
573 double log10_range = log10(im->maxval) - log10(im->minval);
575 ya = pow((double) 10, floor(log10(im->minval)));
576 while (ya < im->minval)
579 return; /* don't have y=10^x gridline */
581 if (yb <= im->maxval) {
582 /* we have at least 2 y=10^x gridlines.
583 Make sure distance between them in pixels
584 are an integer by expanding im->maxval */
585 double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
586 double factor = y_pixel_delta / floor(y_pixel_delta);
587 double new_log10_range = factor * log10_range;
588 double new_ymax_log10 = log10(im->minval) + new_log10_range;
590 im->maxval = pow(10, new_ymax_log10);
591 ytr(im, DNAN); /* reset precalc */
592 log10_range = log10(im->maxval) - log10(im->minval);
594 /* make sure first y=10^x gridline is located on
595 integer pixel position by moving scale slightly
596 downwards (sub-pixel movement) */
597 ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
598 ypixfrac = ypix - floor(ypix);
599 if (ypixfrac > 0 && ypixfrac < 1) {
600 double yfrac = ypixfrac / im->ysize;
602 im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
603 im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
604 ytr(im, DNAN); /* reset precalc */
607 /* Make sure we have an integer pixel distance between
608 each minor gridline */
609 double ypos1 = ytr(im, im->minval);
610 double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
611 double y_pixel_delta = ypos1 - ypos2;
612 double factor = y_pixel_delta / floor(y_pixel_delta);
613 double new_range = factor * (im->maxval - im->minval);
614 double gridstep = im->ygrid_scale.gridstep;
615 double minor_y, minor_y_px, minor_y_px_frac;
617 if (im->maxval > 0.0)
618 im->maxval = im->minval + new_range;
620 im->minval = im->maxval - new_range;
621 ytr(im, DNAN); /* reset precalc */
622 /* make sure first minor gridline is on integer pixel y coord */
623 minor_y = gridstep * floor(im->minval / gridstep);
624 while (minor_y < im->minval)
626 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
627 minor_y_px_frac = minor_y_px - floor(minor_y_px);
628 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
629 double yfrac = minor_y_px_frac / im->ysize;
630 double range = im->maxval - im->minval;
632 im->minval = im->minval - yfrac * range;
633 im->maxval = im->maxval - yfrac * range;
634 ytr(im, DNAN); /* reset precalc */
636 calc_horizontal_grid(im); /* recalc with changed im->maxval */
640 /* reduce data reimplementation by Alex */
643 enum cf_en cf, /* which consolidation function ? */
644 unsigned long cur_step, /* step the data currently is in */
645 time_t *start, /* start, end and step as requested ... */
646 time_t *end, /* ... by the application will be ... */
647 unsigned long *step, /* ... adjusted to represent reality */
648 unsigned long *ds_cnt, /* number of data sources in file */
650 { /* two dimensional array containing the data */
651 int i, reduce_factor = ceil((double) (*step) / (double) cur_step);
652 unsigned long col, dst_row, row_cnt, start_offset, end_offset, skiprows =
654 rrd_value_t *srcptr, *dstptr;
656 (*step) = cur_step * reduce_factor; /* set new step size for reduced data */
659 row_cnt = ((*end) - (*start)) / cur_step;
665 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
666 row_cnt, reduce_factor, *start, *end, cur_step);
667 for (col = 0; col < row_cnt; col++) {
668 printf("time %10lu: ", *start + (col + 1) * cur_step);
669 for (i = 0; i < *ds_cnt; i++)
670 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
675 /* We have to combine [reduce_factor] rows of the source
676 ** into one row for the destination. Doing this we also
677 ** need to take care to combine the correct rows. First
678 ** alter the start and end time so that they are multiples
679 ** of the new step time. We cannot reduce the amount of
680 ** time so we have to move the end towards the future and
681 ** the start towards the past.
683 end_offset = (*end) % (*step);
684 start_offset = (*start) % (*step);
686 /* If there is a start offset (which cannot be more than
687 ** one destination row), skip the appropriate number of
688 ** source rows and one destination row. The appropriate
689 ** number is what we do know (start_offset/cur_step) of
690 ** the new interval (*step/cur_step aka reduce_factor).
693 printf("start_offset: %lu end_offset: %lu\n", start_offset, end_offset);
694 printf("row_cnt before: %lu\n", row_cnt);
697 (*start) = (*start) - start_offset;
698 skiprows = reduce_factor - start_offset / cur_step;
699 srcptr += skiprows * *ds_cnt;
700 for (col = 0; col < (*ds_cnt); col++)
705 printf("row_cnt between: %lu\n", row_cnt);
708 /* At the end we have some rows that are not going to be
709 ** used, the amount is end_offset/cur_step
712 (*end) = (*end) - end_offset + (*step);
713 skiprows = end_offset / cur_step;
717 printf("row_cnt after: %lu\n", row_cnt);
720 /* Sanity check: row_cnt should be multiple of reduce_factor */
721 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
723 if (row_cnt % reduce_factor) {
724 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
725 row_cnt, reduce_factor);
726 printf("BUG in reduce_data()\n");
730 /* Now combine reduce_factor intervals at a time
731 ** into one interval for the destination.
734 for (dst_row = 0; (long int) row_cnt >= reduce_factor; dst_row++) {
735 for (col = 0; col < (*ds_cnt); col++) {
736 rrd_value_t newval = DNAN;
737 unsigned long validval = 0;
739 for (i = 0; i < reduce_factor; i++) {
740 if (isnan(srcptr[i * (*ds_cnt) + col])) {
745 newval = srcptr[i * (*ds_cnt) + col];
754 newval += srcptr[i * (*ds_cnt) + col];
757 newval = min(newval, srcptr[i * (*ds_cnt) + col]);
760 /* an interval contains a failure if any subintervals contained a failure */
762 newval = max(newval, srcptr[i * (*ds_cnt) + col]);
765 newval = srcptr[i * (*ds_cnt) + col];
791 srcptr += (*ds_cnt) * reduce_factor;
792 row_cnt -= reduce_factor;
794 /* If we had to alter the endtime, we didn't have enough
795 ** source rows to fill the last row. Fill it with NaN.
798 for (col = 0; col < (*ds_cnt); col++)
801 row_cnt = ((*end) - (*start)) / *step;
803 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
804 row_cnt, *start, *end, *step);
805 for (col = 0; col < row_cnt; col++) {
806 printf("time %10lu: ", *start + (col + 1) * (*step));
807 for (i = 0; i < *ds_cnt; i++)
808 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
815 /* get the data required for the graphs from the
824 /* pull the data from the rrd files ... */
825 for (i = 0; i < (int) im->gdes_c; i++) {
826 /* only GF_DEF elements fetch data */
827 if (im->gdes[i].gf != GF_DEF)
831 /* do we have it already ? */
832 for (ii = 0; ii < i; ii++) {
833 if (im->gdes[ii].gf != GF_DEF)
835 if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
836 && (im->gdes[i].cf == im->gdes[ii].cf)
837 && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
838 && (im->gdes[i].start_orig == im->gdes[ii].start_orig)
839 && (im->gdes[i].end_orig == im->gdes[ii].end_orig)
840 && (im->gdes[i].step_orig == im->gdes[ii].step_orig)) {
841 /* OK, the data is already there.
842 ** Just copy the header portion
844 im->gdes[i].start = im->gdes[ii].start;
845 im->gdes[i].end = im->gdes[ii].end;
846 im->gdes[i].step = im->gdes[ii].step;
847 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
848 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
849 im->gdes[i].data = im->gdes[ii].data;
850 im->gdes[i].data_first = 0;
857 unsigned long ft_step = im->gdes[i].step; /* ft_step will record what we got from fetch */
858 const char *rrd_daemon;
861 if (im->gdes[i].daemon[0] != 0)
862 rrd_daemon = im->gdes[i].daemon;
864 rrd_daemon = im->daemon_addr;
866 /* "daemon" may be NULL. ENV_RRDCACHED_ADDRESS is evaluated in that
867 * case. If "daemon" holds the same value as in the previous
868 * iteration, no actual new connection is established - the
869 * existing connection is re-used. */
870 rrdc_connect (rrd_daemon);
872 /* If connecting was successfull, use the daemon to query the data.
873 * If there is no connection, for example because no daemon address
874 * was specified, (try to) use the local file directly. */
875 if (rrdc_is_connected (rrd_daemon))
877 status = rrdc_fetch (im->gdes[i].rrd,
878 cf_to_string (im->gdes[i].cf),
883 &im->gdes[i].ds_namv,
890 if ((rrd_fetch_fn(im->gdes[i].rrd,
896 &im->gdes[i].ds_namv,
897 &im->gdes[i].data)) == -1) {
901 im->gdes[i].data_first = 1;
903 /* must reduce to at least im->step
904 otherwhise we end up with more data than we can handle in the
905 chart and visibility of data will be random */
906 im->gdes[i].step = max(im->gdes[i].step,im->step);
907 if (ft_step < im->gdes[i].step) {
908 reduce_data(im->gdes[i].cf_reduce,
913 &im->gdes[i].ds_cnt, &im->gdes[i].data);
915 im->gdes[i].step = ft_step;
919 /* lets see if the required data source is really there */
920 for (ii = 0; ii < (int) im->gdes[i].ds_cnt; ii++) {
921 if (strcmp(im->gdes[i].ds_namv[ii], im->gdes[i].ds_nam) == 0) {
925 if (im->gdes[i].ds == -1) {
926 rrd_set_error("No DS called '%s' in '%s'",
927 im->gdes[i].ds_nam, im->gdes[i].rrd);
935 /* evaluate the expressions in the CDEF functions */
937 /*************************************************************
939 *************************************************************/
941 long find_var_wrapper(
945 return find_var((image_desc_t *) arg1, key);
948 /* find gdes containing var*/
955 for (ii = 0; ii < im->gdes_c - 1; ii++) {
956 if ((im->gdes[ii].gf == GF_DEF
957 || im->gdes[ii].gf == GF_VDEF || im->gdes[ii].gf == GF_CDEF)
958 && (strcmp(im->gdes[ii].vname, key) == 0)) {
965 /* find the greatest common divisor for all the numbers
966 in the 0 terminated num array */
973 for (i = 0; num[i + 1] != 0; i++) {
975 rest = num[i] % num[i + 1];
981 /* return i==0?num[i]:num[i-1]; */
985 /* run the rpn calculator on all the VDEF and CDEF arguments */
992 long *steparray, rpi;
997 rpnstack_init(&rpnstack);
999 for (gdi = 0; gdi < im->gdes_c; gdi++) {
1000 /* Look for GF_VDEF and GF_CDEF in the same loop,
1001 * so CDEFs can use VDEFs and vice versa
1003 switch (im->gdes[gdi].gf) {
1007 graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
1009 /* remove current shift */
1010 vdp->start -= vdp->shift;
1011 vdp->end -= vdp->shift;
1014 if (im->gdes[gdi].shidx >= 0)
1015 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
1018 vdp->shift = im->gdes[gdi].shval;
1020 /* normalize shift to multiple of consolidated step */
1021 vdp->shift = (vdp->shift / (long) vdp->step) * (long) vdp->step;
1024 vdp->start += vdp->shift;
1025 vdp->end += vdp->shift;
1029 /* A VDEF has no DS. This also signals other parts
1030 * of rrdtool that this is a VDEF value, not a CDEF.
1032 im->gdes[gdi].ds_cnt = 0;
1033 if (vdef_calc(im, gdi)) {
1034 rrd_set_error("Error processing VDEF '%s'",
1035 im->gdes[gdi].vname);
1036 rpnstack_free(&rpnstack);
1041 im->gdes[gdi].ds_cnt = 1;
1042 im->gdes[gdi].ds = 0;
1043 im->gdes[gdi].data_first = 1;
1044 im->gdes[gdi].start = 0;
1045 im->gdes[gdi].end = 0;
1050 /* Find the variables in the expression.
1051 * - VDEF variables are substituted by their values
1052 * and the opcode is changed into OP_NUMBER.
1053 * - CDEF variables are analized for their step size,
1054 * the lowest common denominator of all the step
1055 * sizes of the data sources involved is calculated
1056 * and the resulting number is the step size for the
1057 * resulting data source.
1059 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1060 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1061 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1062 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1064 if (im->gdes[ptr].ds_cnt == 0) { /* this is a VDEF data source */
1067 ("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
1068 im->gdes[gdi].vname, im->gdes[ptr].vname);
1069 printf("DEBUG: value from vdef is %f\n",
1070 im->gdes[ptr].vf.val);
1072 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
1073 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
1074 } else { /* normal variables and PREF(variables) */
1076 /* add one entry to the array that keeps track of the step sizes of the
1077 * data sources going into the CDEF. */
1079 (long*)rrd_realloc(steparray,
1081 1) * sizeof(*steparray))) == NULL) {
1082 rrd_set_error("realloc steparray");
1083 rpnstack_free(&rpnstack);
1087 steparray[stepcnt - 1] = im->gdes[ptr].step;
1089 /* adjust start and end of cdef (gdi) so
1090 * that it runs from the latest start point
1091 * to the earliest endpoint of any of the
1092 * rras involved (ptr)
1095 if (im->gdes[gdi].start < im->gdes[ptr].start)
1096 im->gdes[gdi].start = im->gdes[ptr].start;
1098 if (im->gdes[gdi].end == 0 ||
1099 im->gdes[gdi].end > im->gdes[ptr].end)
1100 im->gdes[gdi].end = im->gdes[ptr].end;
1102 /* store pointer to the first element of
1103 * the rra providing data for variable,
1104 * further save step size and data source
1107 im->gdes[gdi].rpnp[rpi].data =
1108 im->gdes[ptr].data + im->gdes[ptr].ds;
1109 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
1110 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
1112 /* backoff the *.data ptr; this is done so
1113 * rpncalc() function doesn't have to treat
1114 * the first case differently
1116 } /* if ds_cnt != 0 */
1117 } /* if OP_VARIABLE */
1118 } /* loop through all rpi */
1120 /* move the data pointers to the correct period */
1121 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1122 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1123 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1124 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1126 im->gdes[gdi].start - im->gdes[ptr].start;
1129 im->gdes[gdi].rpnp[rpi].data +=
1130 (diff / im->gdes[ptr].step) *
1131 im->gdes[ptr].ds_cnt;
1135 if (steparray == NULL) {
1136 rrd_set_error("rpn expressions without DEF"
1137 " or CDEF variables are not supported");
1138 rpnstack_free(&rpnstack);
1141 steparray[stepcnt] = 0;
1142 /* Now find the resulting step. All steps in all
1143 * used RRAs have to be visited
1145 im->gdes[gdi].step = lcd(steparray);
1147 if ((im->gdes[gdi].data = (rrd_value_t*)malloc(((im->gdes[gdi].end -
1148 im->gdes[gdi].start)
1149 / im->gdes[gdi].step)
1150 * sizeof(double))) == NULL) {
1151 rrd_set_error("malloc im->gdes[gdi].data");
1152 rpnstack_free(&rpnstack);
1156 /* Step through the new cdef results array and
1157 * calculate the values
1159 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
1160 now <= im->gdes[gdi].end; now += im->gdes[gdi].step) {
1161 rpnp_t *rpnp = im->gdes[gdi].rpnp;
1163 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
1164 * in this case we are advancing by timesteps;
1165 * we use the fact that time_t is a synonym for long
1167 if (rpn_calc(rpnp, &rpnstack, (long) now,
1168 im->gdes[gdi].data, ++dataidx) == -1) {
1169 /* rpn_calc sets the error string */
1170 rpnstack_free(&rpnstack);
1173 } /* enumerate over time steps within a CDEF */
1178 } /* enumerate over CDEFs */
1179 rpnstack_free(&rpnstack);
1183 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
1184 /* yes we are loosing precision by doing tos with floats instead of doubles
1185 but it seems more stable this way. */
1187 static int AlmostEqual2sComplement(
1193 int aInt = *(int *) &A;
1194 int bInt = *(int *) &B;
1197 /* Make sure maxUlps is non-negative and small enough that the
1198 default NAN won't compare as equal to anything. */
1200 /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
1202 /* Make aInt lexicographically ordered as a twos-complement int */
1205 aInt = 0x80000000l - aInt;
1207 /* Make bInt lexicographically ordered as a twos-complement int */
1210 bInt = 0x80000000l - bInt;
1212 intDiff = abs(aInt - bInt);
1214 if (intDiff <= maxUlps)
1220 /* massage data so, that we get one value for each x coordinate in the graph */
1225 double pixstep = (double) (im->end - im->start)
1226 / (double) im->xsize; /* how much time
1227 passes in one pixel */
1229 double minval = DNAN, maxval = DNAN;
1231 unsigned long gr_time;
1233 /* memory for the processed data */
1234 for (i = 0; i < im->gdes_c; i++) {
1235 if ((im->gdes[i].gf == GF_LINE)
1236 || (im->gdes[i].gf == GF_AREA)
1237 || (im->gdes[i].gf == GF_TICK)
1238 || (im->gdes[i].gf == GF_GRAD)
1240 if ((im->gdes[i].p_data = (rrd_value_t*)malloc((im->xsize + 1)
1241 * sizeof(rrd_value_t))) == NULL) {
1242 rrd_set_error("malloc data_proc");
1248 for (i = 0; i < im->xsize; i++) { /* for each pixel */
1251 gr_time = im->start + pixstep * i; /* time of the current step */
1254 for (ii = 0; ii < im->gdes_c; ii++) {
1257 switch (im->gdes[ii].gf) {
1262 if (!im->gdes[ii].stack)
1264 value = im->gdes[ii].yrule;
1265 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1266 /* The time of the data doesn't necessarily match
1267 ** the time of the graph. Beware.
1269 vidx = im->gdes[ii].vidx;
1270 if (im->gdes[vidx].gf == GF_VDEF) {
1271 value = im->gdes[vidx].vf.val;
1273 if (((long int) gr_time >=
1274 (long int) im->gdes[vidx].start)
1275 && ((long int) gr_time <
1276 (long int) im->gdes[vidx].end)) {
1277 value = im->gdes[vidx].data[(unsigned long)
1283 im->gdes[vidx].step)
1284 * im->gdes[vidx].ds_cnt +
1291 if (!isnan(value)) {
1293 im->gdes[ii].p_data[i] = paintval;
1294 /* GF_TICK: the data values are not
1295 ** relevant for min and max
1297 if (finite(paintval) && im->gdes[ii].gf != GF_TICK) {
1298 if ((isnan(minval) || paintval < minval) &&
1299 !(im->logarithmic && paintval <= 0.0))
1301 if (isnan(maxval) || paintval > maxval)
1305 im->gdes[ii].p_data[i] = DNAN;
1310 ("STACK should already be turned into LINE or AREA here");
1319 /* if min or max have not been asigned a value this is because
1320 there was no data in the graph ... this is not good ...
1321 lets set these to dummy values then ... */
1323 if (im->logarithmic) {
1324 if (isnan(minval) || isnan(maxval) || maxval <= 0) {
1325 minval = 0.0; /* catching this right away below */
1328 /* in logarithm mode, where minval is smaller or equal
1329 to 0 make the beast just way smaller than maxval */
1331 minval = maxval / 10e8;
1334 if (isnan(minval) || isnan(maxval)) {
1340 /* adjust min and max values given by the user */
1341 /* for logscale we add something on top */
1342 if (isnan(im->minval)
1343 || ((!im->rigid) && im->minval > minval)
1345 if (im->logarithmic)
1346 im->minval = minval / 2.0;
1348 im->minval = minval;
1350 if (isnan(im->maxval)
1351 || (!im->rigid && im->maxval < maxval)
1353 if (im->logarithmic)
1354 im->maxval = maxval * 2.0;
1356 im->maxval = maxval;
1359 /* make sure min is smaller than max */
1360 if (im->minval > im->maxval) {
1362 im->minval = 0.99 * im->maxval;
1364 im->minval = 1.01 * im->maxval;
1367 /* make sure min and max are not equal */
1368 if (AlmostEqual2sComplement(im->minval, im->maxval, 4)) {
1374 /* make sure min and max are not both zero */
1375 if (AlmostEqual2sComplement(im->maxval, 0, 4)) {
1382 static int find_first_weekday(void){
1383 static int first_weekday = -1;
1384 if (first_weekday == -1){
1385 #ifdef HAVE__NL_TIME_WEEK_1STDAY
1386 /* according to http://sourceware.org/ml/libc-locales/2009-q1/msg00011.html */
1387 long week_1stday_l = (long) nl_langinfo (_NL_TIME_WEEK_1STDAY);
1388 if (week_1stday_l == 19971130) first_weekday = 0; /* Sun */
1389 else if (week_1stday_l == 19971201) first_weekday = 1; /* Mon */
1390 else first_weekday = 1; /* we go for a monday default */
1395 return first_weekday;
1398 /* identify the point where the first gridline, label ... gets placed */
1400 time_t find_first_time(
1401 time_t start, /* what is the initial time */
1402 enum tmt_en baseint, /* what is the basic interval */
1403 long basestep /* how many if these do we jump a time */
1408 localtime_r(&start, &tm);
1412 tm. tm_sec -= tm.tm_sec % basestep;
1417 tm. tm_min -= tm.tm_min % basestep;
1423 tm. tm_hour -= tm.tm_hour % basestep;
1427 /* we do NOT look at the basestep for this ... */
1434 /* we do NOT look at the basestep for this ... */
1438 tm. tm_mday -= tm.tm_wday - find_first_weekday();
1440 if (tm.tm_wday == 0 && find_first_weekday() > 0)
1441 tm. tm_mday -= 7; /* we want the *previous* week */
1449 tm. tm_mon -= tm.tm_mon % basestep;
1460 tm.tm_year + 1900) %basestep;
1466 /* identify the point where the next gridline, label ... gets placed */
1467 time_t find_next_time(
1468 time_t current, /* what is the initial time */
1469 enum tmt_en baseint, /* what is the basic interval */
1470 long basestep /* how many if these do we jump a time */
1476 localtime_r(¤t, &tm);
1480 case TMT_SECOND: limit = 7200; break;
1481 case TMT_MINUTE: limit = 120; break;
1482 case TMT_HOUR: limit = 2; break;
1483 default: limit = 2; break;
1488 tm. tm_sec += basestep;
1492 tm. tm_min += basestep;
1496 tm. tm_hour += basestep;
1500 tm. tm_mday += basestep;
1504 tm. tm_mday += 7 * basestep;
1508 tm. tm_mon += basestep;
1512 tm. tm_year += basestep;
1514 madetime = mktime(&tm);
1515 } while (madetime == -1 && limit-- >= 0); /* this is necessary to skip impossible times
1516 like the daylight saving time skips */
1522 /* calculate values required for PRINT and GPRINT functions */
1527 long i, ii, validsteps;
1530 int graphelement = 0;
1533 double magfact = -1;
1538 /* wow initializing tmvdef is quite a task :-) */
1539 time_t now = time(NULL);
1541 localtime_r(&now, &tmvdef);
1542 for (i = 0; i < im->gdes_c; i++) {
1543 vidx = im->gdes[i].vidx;
1544 switch (im->gdes[i].gf) {
1547 /* PRINT and GPRINT can now print VDEF generated values.
1548 * There's no need to do any calculations on them as these
1549 * calculations were already made.
1551 if (im->gdes[vidx].gf == GF_VDEF) { /* simply use vals */
1552 printval = im->gdes[vidx].vf.val;
1553 localtime_r(&im->gdes[vidx].vf.when, &tmvdef);
1554 } else { /* need to calculate max,min,avg etcetera */
1555 max_ii = ((im->gdes[vidx].end - im->gdes[vidx].start)
1556 / im->gdes[vidx].step * im->gdes[vidx].ds_cnt);
1559 for (ii = im->gdes[vidx].ds;
1560 ii < max_ii; ii += im->gdes[vidx].ds_cnt) {
1561 if (!finite(im->gdes[vidx].data[ii]))
1563 if (isnan(printval)) {
1564 printval = im->gdes[vidx].data[ii];
1569 switch (im->gdes[i].cf) {
1573 case CF_DEVSEASONAL:
1577 printval += im->gdes[vidx].data[ii];
1580 printval = min(printval, im->gdes[vidx].data[ii]);
1584 printval = max(printval, im->gdes[vidx].data[ii]);
1587 printval = im->gdes[vidx].data[ii];
1590 if (im->gdes[i].cf == CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1591 if (validsteps > 1) {
1592 printval = (printval / validsteps);
1595 } /* prepare printval */
1597 if (!im->gdes[i].strftm && (percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
1598 /* Magfact is set to -1 upon entry to print_calc. If it
1599 * is still less than 0, then we need to run auto_scale.
1600 * Otherwise, put the value into the correct units. If
1601 * the value is 0, then do not set the symbol or magnification
1602 * so next the calculation will be performed again. */
1603 if (magfact < 0.0) {
1604 auto_scale(im, &printval, &si_symb, &magfact);
1605 if (printval == 0.0)
1608 printval /= magfact;
1610 *(++percent_s) = 's';
1611 } else if (!im->gdes[i].strftm && strstr(im->gdes[i].format, "%s") != NULL) {
1612 auto_scale(im, &printval, &si_symb, &magfact);
1615 if (im->gdes[i].gf == GF_PRINT) {
1616 rrd_infoval_t prline;
1618 if (im->gdes[i].strftm) {
1619 prline.u_str = (char*)malloc((FMT_LEG_LEN + 2) * sizeof(char));
1620 if (im->gdes[vidx].vf.never == 1) {
1621 time_clean(prline.u_str, im->gdes[i].format);
1623 strftime(prline.u_str,
1624 FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1626 } else if (bad_format(im->gdes[i].format)) {
1628 ("bad format for PRINT in '%s'", im->gdes[i].format);
1632 sprintf_alloc(im->gdes[i].format, printval, si_symb);
1636 ("print[%ld]", prline_cnt++), RD_I_STR, prline);
1641 if (im->gdes[i].strftm) {
1642 if (im->gdes[vidx].vf.never == 1) {
1643 time_clean(im->gdes[i].legend, im->gdes[i].format);
1645 strftime(im->gdes[i].legend,
1646 FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1649 if (bad_format(im->gdes[i].format)) {
1651 ("bad format for GPRINT in '%s'",
1652 im->gdes[i].format);
1655 #ifdef HAVE_SNPRINTF
1656 snprintf(im->gdes[i].legend,
1658 im->gdes[i].format, printval, si_symb);
1660 sprintf(im->gdes[i].legend,
1661 im->gdes[i].format, printval, si_symb);
1674 if (isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1675 im->gdes[i].yrule = im->gdes[vidx].vf.val;
1680 if (im->gdes[i].xrule == 0) { /* again ... the legend printer needs it */
1681 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1690 #ifdef WITH_PIECHART
1698 ("STACK should already be turned into LINE or AREA here");
1703 return graphelement;
1708 /* place legends with color spots */
1714 int interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1715 int border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1716 int fill = 0, fill_last;
1717 double legendwidth; // = im->ximg - 2 * border;
1719 double leg_x = border;
1720 int leg_y = 0; //im->yimg;
1721 int leg_y_prev = 0; // im->yimg;
1724 int i, ii, mark = 0;
1725 char default_txtalign = TXA_JUSTIFIED; /*default line orientation */
1728 char saved_legend[FMT_LEG_LEN + 5];
1734 legendwidth = im->legendwidth - 2 * border;
1738 if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
1739 if ((legspace = (int*)malloc(im->gdes_c * sizeof(int))) == NULL) {
1740 rrd_set_error("malloc for legspace");
1744 for (i = 0; i < im->gdes_c; i++) {
1745 char prt_fctn; /*special printfunctions */
1747 strcpy(saved_legend, im->gdes[i].legend);
1751 /* hide legends for rules which are not displayed */
1752 if (im->gdes[i].gf == GF_TEXTALIGN) {
1753 default_txtalign = im->gdes[i].txtalign;
1756 if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1757 if (im->gdes[i].gf == GF_HRULE
1758 && (im->gdes[i].yrule <
1759 im->minval || im->gdes[i].yrule > im->maxval))
1760 im->gdes[i].legend[0] = '\0';
1761 if (im->gdes[i].gf == GF_VRULE
1762 && (im->gdes[i].xrule <
1763 im->start || im->gdes[i].xrule > im->end))
1764 im->gdes[i].legend[0] = '\0';
1767 /* turn \\t into tab */
1768 while ((tab = strstr(im->gdes[i].legend, "\\t"))) {
1769 memmove(tab, tab + 1, strlen(tab));
1773 leg_cc = strlen(im->gdes[i].legend);
1774 /* is there a controle code at the end of the legend string ? */
1775 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\') {
1776 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1778 im->gdes[i].legend[leg_cc] = '\0';
1782 /* only valid control codes */
1783 if (prt_fctn != 'l' && prt_fctn != 'n' && /* a synonym for l */
1788 prt_fctn != 's' && prt_fctn != '\0' && prt_fctn != 'g') {
1791 ("Unknown control code at the end of '%s\\%c'",
1792 im->gdes[i].legend, prt_fctn);
1796 if (prt_fctn == 'n') {
1800 /* remove exess space from the end of the legend for \g */
1801 while (prt_fctn == 'g' &&
1802 leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1804 im->gdes[i].legend[leg_cc] = '\0';
1809 /* no interleg space if string ends in \g */
1810 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1812 fill += legspace[i];
1815 gfx_get_text_width(im,
1821 im->tabwidth, im->gdes[i].legend);
1826 /* who said there was a special tag ... ? */
1827 if (prt_fctn == 'g') {
1831 if (prt_fctn == '\0') {
1832 if(calc_width && (fill > legendwidth)){
1835 if (i == im->gdes_c - 1 || fill > legendwidth) {
1836 /* just one legend item is left right or center */
1837 switch (default_txtalign) {
1852 /* is it time to place the legends ? */
1853 if (fill > legendwidth) {
1861 if (leg_c == 1 && prt_fctn == 'j') {
1866 if (prt_fctn != '\0') {
1868 if (leg_c >= 2 && prt_fctn == 'j') {
1869 glue = (double)(legendwidth - fill) / (double)(leg_c - 1);
1873 if (prt_fctn == 'c')
1874 leg_x = border + (double)(legendwidth - fill) / 2.0;
1875 if (prt_fctn == 'r')
1876 leg_x = legendwidth - fill + border;
1877 for (ii = mark; ii <= i; ii++) {
1878 if (im->gdes[ii].legend[0] == '\0')
1879 continue; /* skip empty legends */
1880 im->gdes[ii].leg_x = leg_x;
1881 im->gdes[ii].leg_y = leg_y + border;
1883 (double)gfx_get_text_width(im, leg_x,
1888 im->tabwidth, im->gdes[ii].legend)
1889 +(double)legspace[ii]
1893 if (leg_x > border || prt_fctn == 's')
1894 leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1895 if (prt_fctn == 's')
1896 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1897 if (prt_fctn == 'u')
1898 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size *1.8;
1900 if(calc_width && (fill > legendwidth)){
1909 strcpy(im->gdes[i].legend, saved_legend);
1914 im->legendwidth = legendwidth + 2 * border;
1917 im->legendheight = leg_y + border * 0.6;
1924 /* create a grid on the graph. it determines what to do
1925 from the values of xsize, start and end */
1927 /* the xaxis labels are determined from the number of seconds per pixel
1928 in the requested graph */
1930 int calc_horizontal_grid(
1938 int decimals, fractionals;
1940 im->ygrid_scale.labfact = 2;
1941 range = im->maxval - im->minval;
1942 scaledrange = range / im->magfact;
1943 /* does the scale of this graph make it impossible to put lines
1944 on it? If so, give up. */
1945 if (isnan(scaledrange)) {
1949 /* find grid spaceing */
1951 if (isnan(im->ygridstep)) {
1952 if (im->extra_flags & ALTYGRID) {
1953 /* find the value with max number of digits. Get number of digits */
1956 (max(fabs(im->maxval), fabs(im->minval)) *
1957 im->viewfactor / im->magfact));
1958 if (decimals <= 0) /* everything is small. make place for zero */
1960 im->ygrid_scale.gridstep =
1962 floor(log10(range * im->viewfactor / im->magfact))) /
1963 im->viewfactor * im->magfact;
1964 if (im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1965 im->ygrid_scale.gridstep = 0.1;
1966 /* should have at least 5 lines but no more then 15 */
1967 if (range / im->ygrid_scale.gridstep < 5
1968 && im->ygrid_scale.gridstep >= 30)
1969 im->ygrid_scale.gridstep /= 10;
1970 if (range / im->ygrid_scale.gridstep > 15)
1971 im->ygrid_scale.gridstep *= 10;
1972 if (range / im->ygrid_scale.gridstep > 5) {
1973 im->ygrid_scale.labfact = 1;
1974 if (range / im->ygrid_scale.gridstep > 8
1975 || im->ygrid_scale.gridstep <
1976 1.8 * im->text_prop[TEXT_PROP_AXIS].size)
1977 im->ygrid_scale.labfact = 2;
1979 im->ygrid_scale.gridstep /= 5;
1980 im->ygrid_scale.labfact = 5;
1984 (im->ygrid_scale.gridstep *
1985 (double) im->ygrid_scale.labfact * im->viewfactor /
1987 if (fractionals < 0) { /* small amplitude. */
1988 int len = decimals - fractionals + 1;
1990 if (im->unitslength < len + 2)
1991 im->unitslength = len + 2;
1992 sprintf(im->ygrid_scale.labfmt,
1994 -fractionals, (im->symbol != ' ' ? " %c" : ""));
1996 int len = decimals + 1;
1998 if (im->unitslength < len + 2)
1999 im->unitslength = len + 2;
2000 sprintf(im->ygrid_scale.labfmt,
2001 "%%%d.0f%s", len, (im->symbol != ' ' ? " %c" : ""));
2003 } else { /* classic rrd grid */
2004 for (i = 0; ylab[i].grid > 0; i++) {
2005 pixel = im->ysize / (scaledrange / ylab[i].grid);
2011 for (i = 0; i < 4; i++) {
2012 if (pixel * ylab[gridind].lfac[i] >=
2013 1.8 * im->text_prop[TEXT_PROP_AXIS].size) {
2014 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
2019 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
2022 im->ygrid_scale.gridstep = im->ygridstep;
2023 im->ygrid_scale.labfact = im->ylabfact;
2028 int draw_horizontal_grid(
2034 char graph_label[100];
2036 double X0 = im->xorigin;
2037 double X1 = im->xorigin + im->xsize;
2038 int sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
2039 int egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
2041 double second_axis_magfact = 0;
2042 char *second_axis_symb = "";
2045 im->ygrid_scale.gridstep /
2046 (double) im->magfact * (double) im->viewfactor;
2047 MaxY = scaledstep * (double) egrid;
2048 for (i = sgrid; i <= egrid; i++) {
2050 im->ygrid_scale.gridstep * i);
2052 im->ygrid_scale.gridstep * (i + 1));
2054 if (floor(Y0 + 0.5) >=
2055 im->yorigin - im->ysize && floor(Y0 + 0.5) <= im->yorigin) {
2056 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
2057 with the chosen settings. Add a label if required by settings, or if
2058 there is only one label so far and the next grid line is out of bounds. */
2059 if (i % im->ygrid_scale.labfact == 0
2061 && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
2062 if (im->symbol == ' ') {
2063 if (im->extra_flags & ALTYGRID) {
2064 sprintf(graph_label,
2065 im->ygrid_scale.labfmt,
2066 scaledstep * (double) i);
2069 sprintf(graph_label, "%4.1f",
2070 scaledstep * (double) i);
2072 sprintf(graph_label, "%4.0f",
2073 scaledstep * (double) i);
2077 char sisym = (i == 0 ? ' ' : im->symbol);
2079 if (im->extra_flags & ALTYGRID) {
2080 sprintf(graph_label,
2081 im->ygrid_scale.labfmt,
2082 scaledstep * (double) i, sisym);
2085 sprintf(graph_label, "%4.1f %c",
2086 scaledstep * (double) i, sisym);
2088 sprintf(graph_label, "%4.0f %c",
2089 scaledstep * (double) i, sisym);
2094 if (im->second_axis_scale != 0){
2095 char graph_label_right[100];
2096 double sval = im->ygrid_scale.gridstep*(double)i*im->second_axis_scale+im->second_axis_shift;
2097 if (im->second_axis_format[0] == '\0'){
2098 if (!second_axis_magfact){
2099 double dummy = im->ygrid_scale.gridstep*(double)(sgrid+egrid)/2.0*im->second_axis_scale+im->second_axis_shift;
2100 auto_scale(im,&dummy,&second_axis_symb,&second_axis_magfact);
2102 sval /= second_axis_magfact;
2105 sprintf(graph_label_right,"%5.1f %s",sval,second_axis_symb);
2107 sprintf(graph_label_right,"%5.0f %s",sval,second_axis_symb);
2111 sprintf(graph_label_right,im->second_axis_format,sval);
2115 im->graph_col[GRC_FONT],
2116 im->text_prop[TEXT_PROP_AXIS].font_desc,
2117 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2118 graph_label_right );
2124 text_prop[TEXT_PROP_AXIS].
2126 im->graph_col[GRC_FONT],
2128 text_prop[TEXT_PROP_AXIS].
2131 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2132 gfx_line(im, X0 - 2, Y0, X0, Y0,
2133 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2134 gfx_line(im, X1, Y0, X1 + 2, Y0,
2135 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2136 gfx_dashed_line(im, X0 - 2, Y0,
2142 im->grid_dash_on, im->grid_dash_off);
2143 } else if (!(im->extra_flags & NOMINOR)) {
2146 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2147 gfx_line(im, X1, Y0, X1 + 2, Y0,
2148 GRIDWIDTH, im->graph_col[GRC_GRID]);
2149 gfx_dashed_line(im, X0 - 1, Y0,
2153 graph_col[GRC_GRID],
2154 im->grid_dash_on, im->grid_dash_off);
2161 /* this is frexp for base 10 */
2172 iexp = floor(log((double)fabs(x)) / log((double)10));
2173 mnt = x / pow(10.0, iexp);
2176 mnt = x / pow(10.0, iexp);
2183 /* logaritmic horizontal grid */
2184 int horizontal_log_grid(
2188 double yloglab[][10] = {
2190 1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0,
2192 1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0,
2194 1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0,
2211 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} /* last line */
2213 int i, j, val_exp, min_exp;
2214 double nex; /* number of decades in data */
2215 double logscale; /* scale in logarithmic space */
2216 int exfrac = 1; /* decade spacing */
2217 int mid = -1; /* row in yloglab for major grid */
2218 double mspac; /* smallest major grid spacing (pixels) */
2219 int flab; /* first value in yloglab to use */
2220 double value, tmp, pre_value;
2222 char graph_label[100];
2224 nex = log10(im->maxval / im->minval);
2225 logscale = im->ysize / nex;
2226 /* major spacing for data with high dynamic range */
2227 while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2234 /* major spacing for less dynamic data */
2236 /* search best row in yloglab */
2238 for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2239 mspac = logscale * log10(10.0 / yloglab[mid][i]);
2242 2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
2245 /* find first value in yloglab */
2247 yloglab[mid][flab] < 10
2248 && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2249 if (yloglab[mid][flab] == 10.0) {
2254 if (val_exp % exfrac)
2255 val_exp += abs(-val_exp % exfrac);
2257 X1 = im->xorigin + im->xsize;
2262 value = yloglab[mid][flab] * pow(10.0, val_exp);
2263 if (AlmostEqual2sComplement(value, pre_value, 4))
2264 break; /* it seems we are not converging */
2266 Y0 = ytr(im, value);
2267 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2269 /* major grid line */
2271 X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2272 gfx_line(im, X1, Y0, X1 + 2, Y0,
2273 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2274 gfx_dashed_line(im, X0 - 2, Y0,
2279 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2281 if (im->extra_flags & FORCE_UNITS_SI) {
2286 scale = floor(val_exp / 3.0);
2288 pvalue = pow(10.0, val_exp % 3);
2290 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2291 pvalue *= yloglab[mid][flab];
2292 if (((scale + si_symbcenter) < (int) sizeof(si_symbol))
2293 && ((scale + si_symbcenter) >= 0))
2294 symbol = si_symbol[scale + si_symbcenter];
2297 sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2299 sprintf(graph_label, "%3.0e", value);
2301 if (im->second_axis_scale != 0){
2302 char graph_label_right[100];
2303 double sval = value*im->second_axis_scale+im->second_axis_shift;
2304 if (im->second_axis_format[0] == '\0'){
2305 if (im->extra_flags & FORCE_UNITS_SI) {
2308 auto_scale(im,&sval,&symb,&mfac);
2309 sprintf(graph_label_right,"%4.0f %s", sval,symb);
2312 sprintf(graph_label_right,"%3.0e", sval);
2316 sprintf(graph_label_right,im->second_axis_format,sval,"");
2321 im->graph_col[GRC_FONT],
2322 im->text_prop[TEXT_PROP_AXIS].font_desc,
2323 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2324 graph_label_right );
2330 text_prop[TEXT_PROP_AXIS].
2332 im->graph_col[GRC_FONT],
2334 text_prop[TEXT_PROP_AXIS].
2337 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2339 if (mid < 4 && exfrac == 1) {
2340 /* find first and last minor line behind current major line
2341 * i is the first line and j tha last */
2343 min_exp = val_exp - 1;
2344 for (i = 1; yloglab[mid][i] < 10.0; i++);
2345 i = yloglab[mid][i - 1] + 1;
2349 i = yloglab[mid][flab - 1] + 1;
2350 j = yloglab[mid][flab];
2353 /* draw minor lines below current major line */
2354 for (; i < j; i++) {
2356 value = i * pow(10.0, min_exp);
2357 if (value < im->minval)
2359 Y0 = ytr(im, value);
2360 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2365 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2366 gfx_line(im, X1, Y0, X1 + 2, Y0,
2367 GRIDWIDTH, im->graph_col[GRC_GRID]);
2368 gfx_dashed_line(im, X0 - 1, Y0,
2372 graph_col[GRC_GRID],
2373 im->grid_dash_on, im->grid_dash_off);
2375 } else if (exfrac > 1) {
2376 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2377 value = pow(10.0, i);
2378 if (value < im->minval)
2380 Y0 = ytr(im, value);
2381 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2386 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2387 gfx_line(im, X1, Y0, X1 + 2, Y0,
2388 GRIDWIDTH, im->graph_col[GRC_GRID]);
2389 gfx_dashed_line(im, X0 - 1, Y0,
2393 graph_col[GRC_GRID],
2394 im->grid_dash_on, im->grid_dash_off);
2399 if (yloglab[mid][++flab] == 10.0) {
2405 /* draw minor lines after highest major line */
2406 if (mid < 4 && exfrac == 1) {
2407 /* find first and last minor line below current major line
2408 * i is the first line and j tha last */
2410 min_exp = val_exp - 1;
2411 for (i = 1; yloglab[mid][i] < 10.0; i++);
2412 i = yloglab[mid][i - 1] + 1;
2416 i = yloglab[mid][flab - 1] + 1;
2417 j = yloglab[mid][flab];
2420 /* draw minor lines below current major line */
2421 for (; i < j; i++) {
2423 value = i * pow(10.0, min_exp);
2424 if (value < im->minval)
2426 Y0 = ytr(im, value);
2427 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2431 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2432 gfx_line(im, X1, Y0, X1 + 2, Y0,
2433 GRIDWIDTH, im->graph_col[GRC_GRID]);
2434 gfx_dashed_line(im, X0 - 1, Y0,
2438 graph_col[GRC_GRID],
2439 im->grid_dash_on, im->grid_dash_off);
2442 /* fancy minor gridlines */
2443 else if (exfrac > 1) {
2444 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2445 value = pow(10.0, i);
2446 if (value < im->minval)
2448 Y0 = ytr(im, value);
2449 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2453 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2454 gfx_line(im, X1, Y0, X1 + 2, Y0,
2455 GRIDWIDTH, im->graph_col[GRC_GRID]);
2456 gfx_dashed_line(im, X0 - 1, Y0,
2460 graph_col[GRC_GRID],
2461 im->grid_dash_on, im->grid_dash_off);
2472 int xlab_sel; /* which sort of label and grid ? */
2473 time_t ti, tilab, timajor;
2475 char graph_label[100];
2476 double X0, Y0, Y1; /* points for filled graph and more */
2479 /* the type of time grid is determined by finding
2480 the number of seconds per pixel in the graph */
2481 if (im->xlab_user.minsec == -1) {
2482 factor = (im->end - im->start) / im->xsize;
2484 while (xlab[xlab_sel + 1].minsec !=
2485 -1 && xlab[xlab_sel + 1].minsec <= factor) {
2487 } /* pick the last one */
2488 while (xlab[xlab_sel - 1].minsec ==
2489 xlab[xlab_sel].minsec
2490 && xlab[xlab_sel].length > (im->end - im->start)) {
2492 } /* go back to the smallest size */
2493 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2494 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2495 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2496 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2497 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2498 im->xlab_user.labst = xlab[xlab_sel].labst;
2499 im->xlab_user.precis = xlab[xlab_sel].precis;
2500 im->xlab_user.stst = xlab[xlab_sel].stst;
2503 /* y coords are the same for every line ... */
2505 Y1 = im->yorigin - im->ysize;
2506 /* paint the minor grid */
2507 if (!(im->extra_flags & NOMINOR)) {
2508 for (ti = find_first_time(im->start,
2516 find_first_time(im->start,
2521 ti < im->end && ti != -1;
2523 find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2525 /* are we inside the graph ? */
2526 if (ti < im->start || ti > im->end)
2528 while (timajor < ti && timajor != -1) {
2529 timajor = find_next_time(timajor,
2532 mgridtm, im->xlab_user.mgridst);
2534 if (timajor == -1) break; /* fail in case of problems with time increments */
2536 continue; /* skip as falls on major grid line */
2538 gfx_line(im, X0, Y1 - 2, X0, Y1,
2539 GRIDWIDTH, im->graph_col[GRC_GRID]);
2540 gfx_line(im, X0, Y0, X0, Y0 + 2,
2541 GRIDWIDTH, im->graph_col[GRC_GRID]);
2542 gfx_dashed_line(im, X0, Y0 + 1, X0,
2545 graph_col[GRC_GRID],
2546 im->grid_dash_on, im->grid_dash_off);
2550 /* paint the major grid */
2551 for (ti = find_first_time(im->start,
2558 ti < im->end && ti != -1;
2559 ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2561 /* are we inside the graph ? */
2562 if (ti < im->start || ti > im->end)
2565 gfx_line(im, X0, Y1 - 2, X0, Y1,
2566 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2567 gfx_line(im, X0, Y0, X0, Y0 + 3,
2568 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2569 gfx_dashed_line(im, X0, Y0 + 3, X0,
2573 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2575 /* paint the labels below the graph */
2577 find_first_time(im->start -
2586 im->xlab_user.precis / 2) && ti != -1;
2587 ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2589 tilab = ti + im->xlab_user.precis / 2; /* correct time for the label */
2590 /* are we inside the graph ? */
2591 if (tilab < im->start || tilab > im->end)
2594 localtime_r(&tilab, &tm);
2595 strftime(graph_label, 99, im->xlab_user.stst, &tm);
2597 # error "your libc has no strftime I guess we'll abort the exercise here."
2602 im->graph_col[GRC_FONT],
2604 text_prop[TEXT_PROP_AXIS].
2607 GFX_H_CENTER, GFX_V_TOP, graph_label);
2616 /* draw x and y axis */
2617 /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2618 im->xorigin+im->xsize,im->yorigin-im->ysize,
2619 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2621 gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2622 im->xorigin+im->xsize,im->yorigin-im->ysize,
2623 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2625 gfx_line(im, im->xorigin - 4,
2627 im->xorigin + im->xsize +
2628 4, im->yorigin, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2629 gfx_line(im, im->xorigin,
2632 im->yorigin - im->ysize -
2633 4, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2634 /* arrow for X and Y axis direction */
2635 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 */
2636 im->graph_col[GRC_ARROW]);
2638 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 */
2639 im->graph_col[GRC_ARROW]);
2641 if (im->second_axis_scale != 0){
2642 gfx_line ( im, im->xorigin+im->xsize,im->yorigin+4,
2643 im->xorigin+im->xsize,im->yorigin-im->ysize-4,
2644 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2646 im->xorigin+im->xsize-2, im->yorigin-im->ysize-2,
2647 im->xorigin+im->xsize+3, im->yorigin-im->ysize-2,
2648 im->xorigin+im->xsize, im->yorigin-im->ysize-7, /* LINEOFFSET */
2649 im->graph_col[GRC_ARROW]);
2660 double X0, Y0; /* points for filled graph and more */
2661 struct gfx_color_t water_color;
2663 if (im->draw_3d_border > 0) {
2664 /* draw 3d border */
2665 i = im->draw_3d_border;
2666 gfx_new_area(im, 0, im->yimg,
2667 i, im->yimg - i, i, i, im->graph_col[GRC_SHADEA]);
2668 gfx_add_point(im, im->ximg - i, i);
2669 gfx_add_point(im, im->ximg, 0);
2670 gfx_add_point(im, 0, 0);
2672 gfx_new_area(im, i, im->yimg - i,
2674 im->yimg - i, im->ximg - i, i, im->graph_col[GRC_SHADEB]);
2675 gfx_add_point(im, im->ximg, 0);
2676 gfx_add_point(im, im->ximg, im->yimg);
2677 gfx_add_point(im, 0, im->yimg);
2680 if (im->draw_x_grid == 1)
2682 if (im->draw_y_grid == 1) {
2683 if (im->logarithmic) {
2684 res = horizontal_log_grid(im);
2686 res = draw_horizontal_grid(im);
2689 /* dont draw horizontal grid if there is no min and max val */
2691 char *nodata = "No Data found";
2693 gfx_text(im, im->ximg / 2,
2696 im->graph_col[GRC_FONT],
2698 text_prop[TEXT_PROP_AXIS].
2701 GFX_H_CENTER, GFX_V_CENTER, nodata);
2705 /* yaxis unit description */
2706 if (im->ylegend[0] != '\0'){
2708 im->xOriginLegendY+10,
2710 im->graph_col[GRC_FONT],
2712 text_prop[TEXT_PROP_UNIT].
2715 RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2718 if (im->second_axis_legend[0] != '\0'){
2720 im->xOriginLegendY2+10,
2721 im->yOriginLegendY2,
2722 im->graph_col[GRC_FONT],
2723 im->text_prop[TEXT_PROP_UNIT].font_desc,
2725 RRDGRAPH_YLEGEND_ANGLE,
2726 GFX_H_CENTER, GFX_V_CENTER,
2727 im->second_axis_legend);
2732 im->xOriginTitle, im->yOriginTitle+6,
2733 im->graph_col[GRC_FONT],
2735 text_prop[TEXT_PROP_TITLE].
2737 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP, im->title);
2738 /* rrdtool 'logo' */
2739 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
2740 water_color = im->graph_col[GRC_FONT];
2741 water_color.alpha = 0.3;
2742 double xpos = im->legendposition == EAST ? im->xOriginLegendY : im->ximg - 4;
2743 gfx_text(im, xpos, 5,
2746 text_prop[TEXT_PROP_WATERMARK].
2747 font_desc, im->tabwidth,
2748 -90, GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2750 /* graph watermark */
2751 if (im->watermark[0] != '\0') {
2752 water_color = im->graph_col[GRC_FONT];
2753 water_color.alpha = 0.3;
2755 im->ximg / 2, im->yimg - 6,
2758 text_prop[TEXT_PROP_WATERMARK].
2759 font_desc, im->tabwidth, 0,
2760 GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2764 if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
2765 for (i = 0; i < im->gdes_c; i++) {
2766 if (im->gdes[i].legend[0] == '\0')
2768 /* im->gdes[i].leg_y is the bottom of the legend */
2769 X0 = im->xOriginLegend + im->gdes[i].leg_x;
2770 Y0 = im->legenddirection == TOP_DOWN ? im->yOriginLegend + im->gdes[i].leg_y : im->yOriginLegend + im->legendheight - im->gdes[i].leg_y;
2771 gfx_text(im, X0, Y0,
2772 im->graph_col[GRC_FONT],
2775 [TEXT_PROP_LEGEND].font_desc,
2777 GFX_H_LEFT, GFX_V_BOTTOM, im->gdes[i].legend);
2778 /* The legend for GRAPH items starts with "M " to have
2779 enough space for the box */
2780 if (im->gdes[i].gf != GF_PRINT &&
2781 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2785 boxH = gfx_get_text_width(im, 0,
2790 im->tabwidth, "o") * 1.2;
2792 /* shift the box up a bit */
2795 if (im->dynamic_labels && im->gdes[i].gf == GF_HRULE) { /* [-] */
2797 cairo_new_path(im->cr);
2798 cairo_set_line_width(im->cr, 1.0);
2801 X0 + boxH, Y0 - boxV / 2,
2802 1.0, im->gdes[i].col);
2804 } else if (im->dynamic_labels && im->gdes[i].gf == GF_VRULE) { /* [|] */
2806 cairo_new_path(im->cr);
2807 cairo_set_line_width(im->cr, 1.0);
2810 X0 + boxH / 2, Y0 - boxV,
2811 1.0, im->gdes[i].col);
2813 } else if (im->dynamic_labels && im->gdes[i].gf == GF_LINE) { /* [/] */
2815 cairo_new_path(im->cr);
2816 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
2819 X0 + boxH, Y0 - boxV,
2820 im->gdes[i].linewidth, im->gdes[i].col);
2823 /* make sure transparent colors show up the same way as in the graph */
2826 X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2827 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2829 gfx_new_area(im, X0, Y0 - boxV, X0,
2830 Y0, X0 + boxH, Y0, im->gdes[i].col);
2831 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2834 cairo_new_path(im->cr);
2835 cairo_set_line_width(im->cr, 1.0);
2838 gfx_line_fit(im, &X0, &Y0);
2839 gfx_line_fit(im, &X1, &Y1);
2840 cairo_move_to(im->cr, X0, Y0);
2841 cairo_line_to(im->cr, X1, Y0);
2842 cairo_line_to(im->cr, X1, Y1);
2843 cairo_line_to(im->cr, X0, Y1);
2844 cairo_close_path(im->cr);
2845 cairo_set_source_rgba(im->cr,
2846 im->graph_col[GRC_FRAME].red,
2847 im->graph_col[GRC_FRAME].green,
2848 im->graph_col[GRC_FRAME].blue,
2849 im->graph_col[GRC_FRAME].alpha);
2851 if (im->gdes[i].dash) {
2852 /* make box borders in legend dashed if the graph is dashed */
2856 cairo_set_dash(im->cr, dashes, 1, 0.0);
2858 cairo_stroke(im->cr);
2859 cairo_restore(im->cr);
2866 /*****************************************************
2867 * lazy check make sure we rely need to create this graph
2868 *****************************************************/
2875 struct stat imgstat;
2878 return 0; /* no lazy option */
2879 if (strlen(im->graphfile) == 0)
2880 return 0; /* inmemory option */
2881 if (stat(im->graphfile, &imgstat) != 0)
2882 return 0; /* can't stat */
2883 /* one pixel in the existing graph is more then what we would
2885 if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2887 if ((fd = fopen(im->graphfile, "rb")) == NULL)
2888 return 0; /* the file does not exist */
2889 switch (im->imgformat) {
2891 size = PngSize(fd, &(im->ximg), &(im->yimg));
2901 int graph_size_location(
2906 /* The actual size of the image to draw is determined from
2907 ** several sources. The size given on the command line is
2908 ** the graph area but we need more as we have to draw labels
2909 ** and other things outside the graph area. If the option
2910 ** --full-size-mode is selected the size defines the total
2911 ** image size and the size available for the graph is
2915 /** +---+-----------------------------------+
2916 ** | y |...............graph title.........|
2917 ** | +---+-------------------------------+
2921 ** | s | x | main graph area |
2926 ** | l | b +-------------------------------+
2927 ** | e | l | x axis labels |
2928 ** +---+---+-------------------------------+
2929 ** |....................legends............|
2930 ** +---------------------------------------+
2932 ** +---------------------------------------+
2935 int Xvertical = 0, Xvertical2 = 0, Ytitle =
2936 0, Xylabel = 0, Xmain = 0, Ymain =
2937 0, Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2939 // no legends and no the shall be plotted it's easy
2940 if (im->extra_flags & ONLY_GRAPH) {
2942 im->ximg = im->xsize;
2943 im->yimg = im->ysize;
2944 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;
4164 im->rendered_image_size = 0;
4165 im->rendered_image = NULL;
4169 im->tabwidth = 40.0;
4170 im->title[0] = '\0';
4171 im->unitsexponent = 9999;
4172 im->unitslength = 6;
4173 im->viewfactor = 1.0;
4174 im->watermark[0] = '\0';
4175 im->with_markup = 0;
4177 im->xlab_user.minsec = -1;
4179 im->xOriginLegend = 0;
4180 im->xOriginLegendY = 0;
4181 im->xOriginLegendY2 = 0;
4182 im->xOriginTitle = 0;
4184 im->ygridstep = DNAN;
4186 im->ylegend[0] = '\0';
4187 im->second_axis_scale = 0; /* 0 disables it */
4188 im->second_axis_shift = 0; /* no shift by default */
4189 im->second_axis_legend[0] = '\0';
4190 im->second_axis_format[0] = '\0';
4192 im->yOriginLegend = 0;
4193 im->yOriginLegendY = 0;
4194 im->yOriginLegendY2 = 0;
4195 im->yOriginTitle = 0;
4199 im->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
4200 im->cr = cairo_create(im->surface);
4202 for (i = 0; i < DIM(text_prop); i++) {
4203 im->text_prop[i].size = -1;
4204 im->text_prop[i].font_desc = NULL;
4205 rrd_set_font_desc(im,i, deffont ? deffont : text_prop[i].font,text_prop[i].size);
4208 if (fontmap == NULL){
4209 fontmap = pango_cairo_font_map_get_default();
4212 context = pango_cairo_font_map_create_context((PangoCairoFontMap*)fontmap);
4214 pango_cairo_context_set_resolution(context, 100);
4216 pango_cairo_update_context(im->cr,context);
4218 im->layout = pango_layout_new(context);
4219 g_object_unref (context);
4221 // im->layout = pango_cairo_create_layout(im->cr);
4224 cairo_font_options_set_hint_style
4225 (im->font_options, CAIRO_HINT_STYLE_FULL);
4226 cairo_font_options_set_hint_metrics
4227 (im->font_options, CAIRO_HINT_METRICS_ON);
4228 cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
4232 for (i = 0; i < DIM(graph_col); i++)
4233 im->graph_col[i] = graph_col[i];
4239 void rrd_graph_options(
4246 char *parsetime_error = NULL;
4247 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
4248 time_t start_tmp = 0, end_tmp = 0;
4250 rrd_time_value_t start_tv, end_tv;
4251 long unsigned int color;
4253 /* defines for long options without a short equivalent. should be bytes,
4254 and may not collide with (the ASCII value of) short options */
4255 #define LONGOPT_UNITS_SI 255
4258 struct option long_options[] = {
4259 { "alt-autoscale", no_argument, 0, 'A'},
4260 { "imgformat", required_argument, 0, 'a'},
4261 { "font-smoothing-threshold", required_argument, 0, 'B'},
4262 { "base", required_argument, 0, 'b'},
4263 { "color", required_argument, 0, 'c'},
4264 { "full-size-mode", no_argument, 0, 'D'},
4265 { "daemon", required_argument, 0, 'd'},
4266 { "slope-mode", no_argument, 0, 'E'},
4267 { "end", required_argument, 0, 'e'},
4268 { "force-rules-legend", no_argument, 0, 'F'},
4269 { "imginfo", required_argument, 0, 'f'},
4270 { "graph-render-mode", required_argument, 0, 'G'},
4271 { "no-legend", no_argument, 0, 'g'},
4272 { "height", required_argument, 0, 'h'},
4273 { "no-minor", no_argument, 0, 'I'},
4274 { "interlaced", no_argument, 0, 'i'},
4275 { "alt-autoscale-min", no_argument, 0, 'J'},
4276 { "only-graph", no_argument, 0, 'j'},
4277 { "units-length", required_argument, 0, 'L'},
4278 { "lower-limit", required_argument, 0, 'l'},
4279 { "alt-autoscale-max", no_argument, 0, 'M'},
4280 { "zoom", required_argument, 0, 'm'},
4281 { "no-gridfit", no_argument, 0, 'N'},
4282 { "font", required_argument, 0, 'n'},
4283 { "logarithmic", no_argument, 0, 'o'},
4284 { "pango-markup", no_argument, 0, 'P'},
4285 { "font-render-mode", required_argument, 0, 'R'},
4286 { "rigid", no_argument, 0, 'r'},
4287 { "step", required_argument, 0, 'S'},
4288 { "start", required_argument, 0, 's'},
4289 { "tabwidth", required_argument, 0, 'T'},
4290 { "title", required_argument, 0, 't'},
4291 { "upper-limit", required_argument, 0, 'u'},
4292 { "vertical-label", required_argument, 0, 'v'},
4293 { "watermark", required_argument, 0, 'W'},
4294 { "width", required_argument, 0, 'w'},
4295 { "units-exponent", required_argument, 0, 'X'},
4296 { "x-grid", required_argument, 0, 'x'},
4297 { "alt-y-grid", no_argument, 0, 'Y'},
4298 { "y-grid", required_argument, 0, 'y'},
4299 { "lazy", no_argument, 0, 'z'},
4300 { "units", required_argument, 0, LONGOPT_UNITS_SI},
4301 { "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 */
4302 { "disable-rrdtool-tag",no_argument, 0, 1001},
4303 { "right-axis", required_argument, 0, 1002},
4304 { "right-axis-label", required_argument, 0, 1003},
4305 { "right-axis-format", required_argument, 0, 1004},
4306 { "legend-position", required_argument, 0, 1005},
4307 { "legend-direction", required_argument, 0, 1006},
4308 { "border", required_argument, 0, 1007},
4309 { "grid-dash", required_argument, 0, 1008},
4310 { "dynamic-labels", no_argument, 0, 1009},
4311 { "week-fmt", required_argument, 0, 1010},
4317 opterr = 0; /* initialize getopt */
4318 rrd_parsetime("end-24h", &start_tv);
4319 rrd_parsetime("now", &end_tv);
4321 int option_index = 0;
4323 int col_start, col_end;
4325 opt = getopt_long(argc, argv,
4326 "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",
4327 long_options, &option_index);
4332 im->extra_flags |= NOMINOR;
4335 im->extra_flags |= ALTYGRID;
4338 im->extra_flags |= ALTAUTOSCALE;
4341 im->extra_flags |= ALTAUTOSCALE_MIN;
4344 im->extra_flags |= ALTAUTOSCALE_MAX;
4347 im->extra_flags |= ONLY_GRAPH;
4350 im->extra_flags |= NOLEGEND;
4353 if (strcmp(optarg, "north") == 0) {
4354 im->legendposition = NORTH;
4355 } else if (strcmp(optarg, "west") == 0) {
4356 im->legendposition = WEST;
4357 } else if (strcmp(optarg, "south") == 0) {
4358 im->legendposition = SOUTH;
4359 } else if (strcmp(optarg, "east") == 0) {
4360 im->legendposition = EAST;
4362 rrd_set_error("unknown legend-position '%s'", optarg);
4367 if (strcmp(optarg, "topdown") == 0) {
4368 im->legenddirection = TOP_DOWN;
4369 } else if (strcmp(optarg, "bottomup") == 0) {
4370 im->legenddirection = BOTTOM_UP;
4372 rrd_set_error("unknown legend-position '%s'", optarg);
4377 im->extra_flags |= FORCE_RULES_LEGEND;
4380 im->extra_flags |= NO_RRDTOOL_TAG;
4382 case LONGOPT_UNITS_SI:
4383 if (im->extra_flags & FORCE_UNITS) {
4384 rrd_set_error("--units can only be used once!");
4387 if (strcmp(optarg, "si") == 0)
4388 im->extra_flags |= FORCE_UNITS_SI;
4390 rrd_set_error("invalid argument for --units: %s", optarg);
4395 im->unitsexponent = atoi(optarg);
4398 im->unitslength = atoi(optarg);
4399 im->forceleftspace = 1;
4402 im->tabwidth = atof(optarg);
4405 im->step = atoi(optarg);
4411 im->with_markup = 1;
4414 if ((parsetime_error = rrd_parsetime(optarg, &start_tv))) {
4415 rrd_set_error("start time: %s", parsetime_error);
4420 if ((parsetime_error = rrd_parsetime(optarg, &end_tv))) {
4421 rrd_set_error("end time: %s", parsetime_error);
4426 if (strcmp(optarg, "none") == 0) {
4427 im->draw_x_grid = 0;
4431 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
4433 &im->xlab_user.gridst,
4435 &im->xlab_user.mgridst,
4437 &im->xlab_user.labst,
4438 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
4439 strncpy(im->xlab_form, optarg + stroff,
4440 sizeof(im->xlab_form) - 1);
4441 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
4443 (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
4444 rrd_set_error("unknown keyword %s", scan_gtm);
4447 (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
4449 rrd_set_error("unknown keyword %s", scan_mtm);
4452 (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
4453 rrd_set_error("unknown keyword %s", scan_ltm);
4456 im->xlab_user.minsec = 1;
4457 im->xlab_user.stst = im->xlab_form;
4459 rrd_set_error("invalid x-grid format");
4465 if (strcmp(optarg, "none") == 0) {
4466 im->draw_y_grid = 0;
4469 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
4470 if (im->ygridstep <= 0) {
4471 rrd_set_error("grid step must be > 0");
4473 } else if (im->ylabfact < 1) {
4474 rrd_set_error("label factor must be > 0");
4478 rrd_set_error("invalid y-grid format");
4483 im->draw_3d_border = atoi(optarg);
4485 case 1008: /* grid-dash */
4489 &im->grid_dash_off) != 2) {
4490 rrd_set_error("expected grid-dash format float:float");
4494 case 1009: /* enable dynamic labels */
4495 im->dynamic_labels = 1;
4498 strncpy(week_fmt,optarg,sizeof week_fmt);
4499 week_fmt[(sizeof week_fmt)-1]='\0';
4501 case 1002: /* right y axis */
4505 &im->second_axis_scale,
4506 &im->second_axis_shift) == 2) {
4507 if(im->second_axis_scale==0){
4508 rrd_set_error("the second_axis_scale must not be 0");
4512 rrd_set_error("invalid right-axis format expected scale:shift");
4517 strncpy(im->second_axis_legend,optarg,150);
4518 im->second_axis_legend[150]='\0';
4521 if (bad_format(optarg)){
4522 rrd_set_error("use either %le or %lf formats");
4525 strncpy(im->second_axis_format,optarg,150);
4526 im->second_axis_format[150]='\0';
4529 strncpy(im->ylegend, optarg, 150);
4530 im->ylegend[150] = '\0';
4533 im->maxval = atof(optarg);
4536 im->minval = atof(optarg);
4539 im->base = atol(optarg);
4540 if (im->base != 1024 && im->base != 1000) {
4542 ("the only sensible value for base apart from 1000 is 1024");
4547 long_tmp = atol(optarg);
4548 if (long_tmp < 10) {
4549 rrd_set_error("width below 10 pixels");
4552 im->xsize = long_tmp;
4555 long_tmp = atol(optarg);
4556 if (long_tmp < 10) {
4557 rrd_set_error("height below 10 pixels");
4560 im->ysize = long_tmp;
4563 im->extra_flags |= FULL_SIZE_MODE;
4566 /* interlaced png not supported at the moment */
4572 im->imginfo = optarg;
4576 (im->imgformat = if_conv(optarg)) == -1) {
4577 rrd_set_error("unsupported graphics format '%s'", optarg);
4588 im->logarithmic = 1;
4592 "%10[A-Z]#%n%8lx%n",
4593 col_nam, &col_start, &color, &col_end) == 2) {
4595 int col_len = col_end - col_start;
4600 (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4608 (((color & 0xF000) *
4609 0x11000) | ((color & 0x0F00) *
4610 0x01100) | ((color &
4613 ((color & 0x000F) * 0x00011)
4617 color = (color << 8) + 0xff /* shift left by 8 */ ;
4622 rrd_set_error("the color format is #RRGGBB[AA]");
4625 if ((ci = grc_conv(col_nam)) != -1) {
4626 im->graph_col[ci] = gfx_hex_to_col(color);
4628 rrd_set_error("invalid color name '%s'", col_nam);
4632 rrd_set_error("invalid color def format");
4641 if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4642 int sindex, propidx;
4644 if ((sindex = text_prop_conv(prop)) != -1) {
4645 for (propidx = sindex;
4646 propidx < TEXT_PROP_LAST; propidx++) {
4648 rrd_set_font_desc(im,propidx,NULL,size);
4650 if ((int) strlen(optarg) > end+2) {
4651 if (optarg[end] == ':') {
4652 rrd_set_font_desc(im,propidx,optarg + end + 1,0);
4655 ("expected : after font size in '%s'",
4660 /* only run the for loop for DEFAULT (0) for
4661 all others, we break here. woodo programming */
4662 if (propidx == sindex && sindex != 0)
4666 rrd_set_error("invalid fonttag '%s'", prop);
4670 rrd_set_error("invalid text property format");
4676 im->zoom = atof(optarg);
4677 if (im->zoom <= 0.0) {
4678 rrd_set_error("zoom factor must be > 0");
4683 strncpy(im->title, optarg, 150);
4684 im->title[150] = '\0';
4687 if (strcmp(optarg, "normal") == 0) {
4688 cairo_font_options_set_antialias
4689 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4690 cairo_font_options_set_hint_style
4691 (im->font_options, CAIRO_HINT_STYLE_FULL);
4692 } else if (strcmp(optarg, "light") == 0) {
4693 cairo_font_options_set_antialias
4694 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4695 cairo_font_options_set_hint_style
4696 (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4697 } else if (strcmp(optarg, "mono") == 0) {
4698 cairo_font_options_set_antialias
4699 (im->font_options, CAIRO_ANTIALIAS_NONE);
4700 cairo_font_options_set_hint_style
4701 (im->font_options, CAIRO_HINT_STYLE_FULL);
4703 rrd_set_error("unknown font-render-mode '%s'", optarg);
4708 if (strcmp(optarg, "normal") == 0)
4709 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4710 else if (strcmp(optarg, "mono") == 0)
4711 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4713 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4718 /* not supported curently */
4721 strncpy(im->watermark, optarg, 100);
4722 im->watermark[99] = '\0';
4726 if (im->daemon_addr != NULL)
4728 rrd_set_error ("You cannot specify --daemon "
4733 im->daemon_addr = strdup(optarg);
4734 if (im->daemon_addr == NULL)
4736 rrd_set_error("strdup failed");
4744 rrd_set_error("unknown option '%c'", optopt);
4746 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4751 pango_cairo_context_set_font_options(pango_layout_get_context(im->layout), im->font_options);
4752 pango_layout_context_changed(im->layout);
4756 if (im->logarithmic && im->minval <= 0) {
4758 ("for a logarithmic yaxis you must specify a lower-limit > 0");
4762 if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4763 /* error string is set in rrd_parsetime.c */
4767 if (start_tmp < 3600 * 24 * 365 * 10) {
4769 ("the first entry to fetch should be after 1980 (%ld)",
4774 if (end_tmp < start_tmp) {
4776 ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4780 im->start = start_tmp;
4782 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4785 int rrd_graph_color(
4793 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4795 color = strstr(var, "#");
4796 if (color == NULL) {
4797 if (optional == 0) {
4798 rrd_set_error("Found no color in %s", err);
4805 long unsigned int col;
4807 rest = strstr(color, ":");
4814 sscanf(color, "#%6lx%n", &col, &n);
4815 col = (col << 8) + 0xff /* shift left by 8 */ ;
4817 rrd_set_error("Color problem in %s", err);
4820 sscanf(color, "#%8lx%n", &col, &n);
4824 rrd_set_error("Color problem in %s", err);
4826 if (rrd_test_error())
4828 gdp->col = gfx_hex_to_col(col);
4841 while (*ptr != '\0')
4842 if (*ptr++ == '%') {
4844 /* line cannot end with percent char */
4847 /* '%s', '%S' and '%%' are allowed */
4848 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4850 /* %c is allowed (but use only with vdef!) */
4851 else if (*ptr == 'c') {
4856 /* or else '% 6.2lf' and such are allowed */
4858 /* optional padding character */
4859 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4861 /* This should take care of 'm.n' with all three optional */
4862 while (*ptr >= '0' && *ptr <= '9')
4866 while (*ptr >= '0' && *ptr <= '9')
4868 /* Either 'le', 'lf' or 'lg' must follow here */
4871 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4886 const char *const str)
4888 /* A VDEF currently is either "func" or "param,func"
4889 * so the parsing is rather simple. Change if needed.
4896 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4897 if (n == (int) strlen(str)) { /* matched */
4901 sscanf(str, "%29[A-Z]%n", func, &n);
4902 if (n == (int) strlen(str)) { /* matched */
4906 ("Unknown function string '%s' in VDEF '%s'",
4911 if (!strcmp("PERCENT", func))
4912 gdes->vf.op = VDEF_PERCENT;
4913 else if (!strcmp("PERCENTNAN", func))
4914 gdes->vf.op = VDEF_PERCENTNAN;
4915 else if (!strcmp("MAXIMUM", func))
4916 gdes->vf.op = VDEF_MAXIMUM;
4917 else if (!strcmp("AVERAGE", func))
4918 gdes->vf.op = VDEF_AVERAGE;
4919 else if (!strcmp("STDEV", func))
4920 gdes->vf.op = VDEF_STDEV;
4921 else if (!strcmp("MINIMUM", func))
4922 gdes->vf.op = VDEF_MINIMUM;
4923 else if (!strcmp("TOTAL", func))
4924 gdes->vf.op = VDEF_TOTAL;
4925 else if (!strcmp("FIRST", func))
4926 gdes->vf.op = VDEF_FIRST;
4927 else if (!strcmp("LAST", func))
4928 gdes->vf.op = VDEF_LAST;
4929 else if (!strcmp("LSLSLOPE", func))
4930 gdes->vf.op = VDEF_LSLSLOPE;
4931 else if (!strcmp("LSLINT", func))
4932 gdes->vf.op = VDEF_LSLINT;
4933 else if (!strcmp("LSLCORREL", func))
4934 gdes->vf.op = VDEF_LSLCORREL;
4937 ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
4940 switch (gdes->vf.op) {
4942 case VDEF_PERCENTNAN:
4943 if (isnan(param)) { /* no parameter given */
4945 ("Function '%s' needs parameter in VDEF '%s'\n",
4949 if (param >= 0.0 && param <= 100.0) {
4950 gdes->vf.param = param;
4951 gdes->vf.val = DNAN; /* undefined */
4952 gdes->vf.when = 0; /* undefined */
4956 ("Parameter '%f' out of range in VDEF '%s'\n",
4957 param, gdes->vname);
4970 case VDEF_LSLCORREL:
4972 gdes->vf.param = DNAN;
4973 gdes->vf.val = DNAN;
4978 ("Function '%s' needs no parameter in VDEF '%s'\n",
4992 graph_desc_t *src, *dst;
4996 dst = &im->gdes[gdi];
4997 src = &im->gdes[dst->vidx];
4998 data = src->data + src->ds;
5000 steps = (src->end - src->start) / src->step;
5003 ("DEBUG: start == %lu, end == %lu, %lu steps\n",
5004 src->start, src->end, steps);
5006 switch (dst->vf.op) {
5010 if ((array = (rrd_value_t*)malloc(steps * sizeof(double))) == NULL) {
5011 rrd_set_error("malloc VDEV_PERCENT");
5014 for (step = 0; step < steps; step++) {
5015 array[step] = data[step * src->ds_cnt];
5017 qsort(array, step, sizeof(double), vdef_percent_compar);
5018 field = round((dst->vf.param * (double)(steps - 1)) / 100.0);
5019 dst->vf.val = array[field];
5020 dst->vf.when = 0; /* no time component */
5024 for (step = 0; step < steps; step++)
5025 printf("DEBUG: %3li:%10.2f %c\n",
5026 step, array[step], step == field ? '*' : ' ');
5030 case VDEF_PERCENTNAN:{
5033 /* count number of "valid" values */
5035 for (step = 0; step < steps; step++) {
5036 if (!isnan(data[step * src->ds_cnt])) { nancount++; }
5038 /* and allocate it */
5039 if ((array = (rrd_value_t*)malloc(nancount * sizeof(double))) == NULL) {
5040 rrd_set_error("malloc VDEV_PERCENT");
5043 /* and fill it in */
5045 for (step = 0; step < steps; step++) {
5046 if (!isnan(data[step * src->ds_cnt])) {
5047 array[field] = data[step * src->ds_cnt];
5051 qsort(array, nancount, sizeof(double), vdef_percent_compar);
5052 field = round( dst->vf.param * (double)(nancount - 1) / 100.0);
5053 dst->vf.val = array[field];
5054 dst->vf.when = 0; /* no time component */
5061 while (step != steps && isnan(data[step * src->ds_cnt]))
5063 if (step == steps) {
5068 dst->vf.val = data[step * src->ds_cnt];
5069 dst->vf.when = src->start + (step + 1) * src->step;
5072 while (step != steps) {
5073 if (finite(data[step * src->ds_cnt])) {
5074 if (data[step * src->ds_cnt] > dst->vf.val) {
5075 dst->vf.val = data[step * src->ds_cnt];
5076 dst->vf.when = src->start + (step + 1) * src->step;
5088 double average = 0.0;
5090 for (step = 0; step < steps; step++) {
5091 if (finite(data[step * src->ds_cnt])) {
5092 sum += data[step * src->ds_cnt];
5097 if (dst->vf.op == VDEF_TOTAL) {
5098 dst->vf.val = sum * src->step;
5099 dst->vf.when = 0; /* no time component */
5101 } else if (dst->vf.op == VDEF_AVERAGE) {
5102 dst->vf.val = sum / cnt;
5103 dst->vf.when = 0; /* no time component */
5106 average = sum / cnt;
5108 for (step = 0; step < steps; step++) {
5109 if (finite(data[step * src->ds_cnt])) {
5110 sum += pow((data[step * src->ds_cnt] - average), 2.0);
5113 dst->vf.val = pow(sum / cnt, 0.5);
5114 dst->vf.when = 0; /* no time component */
5126 while (step != steps && isnan(data[step * src->ds_cnt]))
5128 if (step == steps) {
5133 dst->vf.val = data[step * src->ds_cnt];
5134 dst->vf.when = src->start + (step + 1) * src->step;
5137 while (step != steps) {
5138 if (finite(data[step * src->ds_cnt])) {
5139 if (data[step * src->ds_cnt] < dst->vf.val) {
5140 dst->vf.val = data[step * src->ds_cnt];
5141 dst->vf.when = src->start + (step + 1) * src->step;
5149 /* The time value returned here is one step before the
5150 * actual time value. This is the start of the first
5154 while (step != steps && isnan(data[step * src->ds_cnt]))
5156 if (step == steps) { /* all entries were NaN */
5161 dst->vf.val = data[step * src->ds_cnt];
5162 dst->vf.when = src->start + step * src->step;
5167 /* The time value returned here is the
5168 * actual time value. This is the end of the last
5172 while (step >= 0 && isnan(data[step * src->ds_cnt]))
5174 if (step < 0) { /* all entries were NaN */
5179 dst->vf.val = data[step * src->ds_cnt];
5180 dst->vf.when = src->start + (step + 1) * src->step;
5186 case VDEF_LSLCORREL:{
5187 /* Bestfit line by linear least squares method */
5190 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
5197 for (step = 0; step < steps; step++) {
5198 if (finite(data[step * src->ds_cnt])) {
5201 SUMxx += step * step;
5202 SUMxy += step * data[step * src->ds_cnt];
5203 SUMy += data[step * src->ds_cnt];
5204 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
5208 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
5209 y_intercept = (SUMy - slope * SUMx) / cnt;
5212 (SUMx * SUMy) / cnt) /
5214 (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
5216 if (dst->vf.op == VDEF_LSLSLOPE) {
5217 dst->vf.val = slope;
5220 } else if (dst->vf.op == VDEF_LSLINT) {
5221 dst->vf.val = y_intercept;
5224 } else if (dst->vf.op == VDEF_LSLCORREL) {
5225 dst->vf.val = correl;
5240 /* NaN < -INF < finite_values < INF */
5241 int vdef_percent_compar(
5247 /* Equality is not returned; this doesn't hurt except
5248 * (maybe) for a little performance.
5251 /* First catch NaN values. They are smallest */
5252 if (isnan(*(double *) a))
5254 if (isnan(*(double *) b))
5256 /* NaN doesn't reach this part so INF and -INF are extremes.
5257 * The sign from isinf() is compatible with the sign we return
5259 if (isinf(*(double *) a))
5260 return isinf(*(double *) a);
5261 if (isinf(*(double *) b))
5262 return isinf(*(double *) b);
5263 /* If we reach this, both values must be finite */
5264 if (*(double *) a < *(double *) b)
5273 rrd_info_type_t type,
5274 rrd_infoval_t value)
5276 im->grinfo_current = rrd_info_push(im->grinfo_current, key, type, value);
5277 if (im->grinfo == NULL) {
5278 im->grinfo = im->grinfo_current;
5289 /* Handling based on
5290 - ANSI C99 Specifications http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf
5291 - Single UNIX Specification version 2 http://www.opengroup.org/onlinepubs/007908799/xsh/strftime.html
5292 - POSIX:2001/Single UNIX Specification version 3 http://www.opengroup.org/onlinepubs/009695399/functions/strftime.html
5293 - POSIX:2008 Specifications http://www.opengroup.org/onlinepubs/9699919799/functions/strftime.html
5294 Specifications tells
5295 "If a conversion specifier is not one of the above, the behavior is undefined."
5298 "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.
5301 "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."
5303 POSIX:2008 introduce more complexe behavior that are not handled here.
5305 According to this, this code will replace:
5306 - % followed by @ by a %@
5307 - % followed by by a %SPACE
5308 - % followed by . by a %.
5309 - % followed by % by a %
5310 - % followed by t by a TAB
5311 - % followed by E then anything by '-'
5312 - % followed by O then anything by '-'
5313 - % followed by anything else by at least one '-'. More characters may be added to better fit expected output length
5317 for(j = 0; (j < FMT_LEG_LEN - 1) && (jj < FMT_LEG_LEN); j++) { /* we don't need to parse the last char */
5318 if (format[j] == '%') {
5319 if ((format[j+1] == 'E') || (format[j+1] == 'O')) {
5321 j+=2; /* We skip next 2 following char */
5322 } else if ((format[j+1] == 'C') || (format[j+1] == 'd') ||
5323 (format[j+1] == 'g') || (format[j+1] == 'H') ||
5324 (format[j+1] == 'I') || (format[j+1] == 'm') ||
5325 (format[j+1] == 'M') || (format[j+1] == 'S') ||
5326 (format[j+1] == 'U') || (format[j+1] == 'V') ||
5327 (format[j+1] == 'W') || (format[j+1] == 'y')) {
5329 if (jj < FMT_LEG_LEN) {
5332 j++; /* We skip the following char */
5333 } else if (format[j+1] == 'j') {
5335 if (jj < FMT_LEG_LEN - 1) {
5339 j++; /* We skip the following char */
5340 } else if ((format[j+1] == 'G') || (format[j+1] == 'Y')) {
5341 /* Assuming Year on 4 digit */
5343 if (jj < FMT_LEG_LEN - 2) {
5348 j++; /* We skip the following char */
5349 } else if (format[j+1] == 'R') {
5351 if (jj < FMT_LEG_LEN - 3) {
5357 j++; /* We skip the following char */
5358 } else if (format[j+1] == 'T') {
5360 if (jj < FMT_LEG_LEN - 6) {
5369 j++; /* We skip the following char */
5370 } else if (format[j+1] == 'F') {
5372 if (jj < FMT_LEG_LEN - 8) {
5383 j++; /* We skip the following char */
5384 } else if (format[j+1] == 'D') {
5386 if (jj < FMT_LEG_LEN - 6) {
5395 j++; /* We skip the following char */
5396 } else if (format[j+1] == 'n') {
5397 result[jj++] = '\r';
5398 result[jj++] = '\n';
5399 j++; /* We skip the following char */
5400 } else if (format[j+1] == 't') {
5401 result[jj++] = '\t';
5402 j++; /* We skip the following char */
5403 } else if (format[j+1] == '%') {
5405 j++; /* We skip the following char */
5406 } else if (format[j+1] == ' ') {
5407 if (jj < FMT_LEG_LEN - 1) {
5411 j++; /* We skip the following char */
5412 } else if (format[j+1] == '.') {
5413 if (jj < FMT_LEG_LEN - 1) {
5417 j++; /* We skip the following char */
5418 } else if (format[j+1] == '@') {
5419 if (jj < FMT_LEG_LEN - 1) {
5423 j++; /* We skip the following char */
5426 j++; /* We skip the following char */
5429 result[jj++] = format[j];
5432 result[jj] = '\0'; /* We must force the end of the string */