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);
259 conv_if(XML, IF_XML);
260 conv_if(XMLENUM, IF_XMLENUM);
261 conv_if(CSV, IF_CSV);
262 conv_if(TSV, IF_TSV);
263 conv_if(SSV, IF_SSV);
264 conv_if(JSON, IF_JSON);
265 conv_if(JSONTIME, IF_JSONTIME);
267 return (enum gfx_if_en)(-1);
270 enum tmt_en tmt_conv(
274 conv_if(SECOND, TMT_SECOND);
275 conv_if(MINUTE, TMT_MINUTE);
276 conv_if(HOUR, TMT_HOUR);
277 conv_if(DAY, TMT_DAY);
278 conv_if(WEEK, TMT_WEEK);
279 conv_if(MONTH, TMT_MONTH);
280 conv_if(YEAR, TMT_YEAR);
281 return (enum tmt_en)(-1);
284 enum grc_en grc_conv(
288 conv_if(BACK, GRC_BACK);
289 conv_if(CANVAS, GRC_CANVAS);
290 conv_if(SHADEA, GRC_SHADEA);
291 conv_if(SHADEB, GRC_SHADEB);
292 conv_if(GRID, GRC_GRID);
293 conv_if(MGRID, GRC_MGRID);
294 conv_if(FONT, GRC_FONT);
295 conv_if(ARROW, GRC_ARROW);
296 conv_if(AXIS, GRC_AXIS);
297 conv_if(FRAME, GRC_FRAME);
299 return (enum grc_en)(-1);
302 enum text_prop_en text_prop_conv(
306 conv_if(DEFAULT, TEXT_PROP_DEFAULT);
307 conv_if(TITLE, TEXT_PROP_TITLE);
308 conv_if(AXIS, TEXT_PROP_AXIS);
309 conv_if(UNIT, TEXT_PROP_UNIT);
310 conv_if(LEGEND, TEXT_PROP_LEGEND);
311 conv_if(WATERMARK, TEXT_PROP_WATERMARK);
312 return (enum text_prop_en)(-1);
322 cairo_status_t status = (cairo_status_t) 0;
327 if (im->daemon_addr != NULL)
328 free(im->daemon_addr);
330 for (i = 0; i < (unsigned) im->gdes_c; i++) {
331 if (im->gdes[i].data_first) {
332 /* careful here, because a single pointer can occur several times */
333 free(im->gdes[i].data);
334 if (im->gdes[i].ds_namv) {
335 for (ii = 0; ii < im->gdes[i].ds_cnt; ii++)
336 free(im->gdes[i].ds_namv[ii]);
337 free(im->gdes[i].ds_namv);
340 /* free allocated memory used for dashed lines */
341 if (im->gdes[i].p_dashes != NULL)
342 free(im->gdes[i].p_dashes);
344 free(im->gdes[i].p_data);
345 free(im->gdes[i].rpnp);
349 for (i = 0; i < DIM(text_prop);i++){
350 pango_font_description_free(im->text_prop[i].font_desc);
351 im->text_prop[i].font_desc = NULL;
354 if (im->font_options)
355 cairo_font_options_destroy(im->font_options);
358 status = cairo_status(im->cr);
359 cairo_destroy(im->cr);
363 if (im->rendered_image) {
364 free(im->rendered_image);
368 g_object_unref (im->layout);
372 cairo_surface_destroy(im->surface);
375 fprintf(stderr, "OOPS: Cairo has issues it can't even die: %s\n",
376 cairo_status_to_string(status));
381 /* find SI magnitude symbol for the given number*/
383 image_desc_t *im, /* image description */
389 char *symbol[] = { "a", /* 10e-18 Atto */
390 "f", /* 10e-15 Femto */
391 "p", /* 10e-12 Pico */
392 "n", /* 10e-9 Nano */
393 "u", /* 10e-6 Micro */
394 "m", /* 10e-3 Milli */
399 "T", /* 10e12 Tera */
400 "P", /* 10e15 Peta */
407 if (*value == 0.0 || isnan(*value)) {
411 sindex = floor(log(fabs(*value)) / log((double) im->base));
412 *magfact = pow((double) im->base, (double) sindex);
413 (*value) /= (*magfact);
415 if (sindex <= symbcenter && sindex >= -symbcenter) {
416 (*symb_ptr) = symbol[sindex + symbcenter];
424 static char si_symbol[] = {
425 'y', /* 10e-24 Yocto */
426 'z', /* 10e-21 Zepto */
427 'a', /* 10e-18 Atto */
428 'f', /* 10e-15 Femto */
429 'p', /* 10e-12 Pico */
430 'n', /* 10e-9 Nano */
431 'u', /* 10e-6 Micro */
432 'm', /* 10e-3 Milli */
437 'T', /* 10e12 Tera */
438 'P', /* 10e15 Peta */
440 'Z', /* 10e21 Zeta */
441 'Y' /* 10e24 Yotta */
443 static const int si_symbcenter = 8;
445 /* find SI magnitude symbol for the numbers on the y-axis*/
447 image_desc_t *im /* image description */
451 double digits, viewdigits = 0;
454 floor(log(max(fabs(im->minval), fabs(im->maxval))) /
455 log((double) im->base));
457 if (im->unitsexponent != 9999) {
458 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
459 viewdigits = floor((double)(im->unitsexponent / 3));
464 im->magfact = pow((double) im->base, digits);
467 printf("digits %6.3f im->magfact %6.3f\n", digits, im->magfact);
470 im->viewfactor = im->magfact / pow((double) im->base, viewdigits);
472 if (((viewdigits + si_symbcenter) < sizeof(si_symbol)) &&
473 ((viewdigits + si_symbcenter) >= 0))
474 im->symbol = si_symbol[(int) viewdigits + si_symbcenter];
479 /* move min and max values around to become sensible */
484 double sensiblevalues[] = { 1000.0, 900.0, 800.0, 750.0, 700.0,
485 600.0, 500.0, 400.0, 300.0, 250.0,
486 200.0, 125.0, 100.0, 90.0, 80.0,
487 75.0, 70.0, 60.0, 50.0, 40.0, 30.0,
488 25.0, 20.0, 10.0, 9.0, 8.0,
489 7.0, 6.0, 5.0, 4.0, 3.5, 3.0,
490 2.5, 2.0, 1.8, 1.5, 1.2, 1.0,
491 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0, -1
494 double scaled_min, scaled_max;
501 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
502 im->minval, im->maxval, im->magfact);
505 if (isnan(im->ygridstep)) {
506 if (im->extra_flags & ALTAUTOSCALE) {
507 /* measure the amplitude of the function. Make sure that
508 graph boundaries are slightly higher then max/min vals
509 so we can see amplitude on the graph */
512 delt = im->maxval - im->minval;
514 fact = 2.0 * pow(10.0,
516 (max(fabs(im->minval), fabs(im->maxval)) /
519 adj = (fact - delt) * 0.55;
522 ("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n",
523 im->minval, im->maxval, delt, fact, adj);
528 } else if (im->extra_flags & ALTAUTOSCALE_MIN) {
529 /* measure the amplitude of the function. Make sure that
530 graph boundaries are slightly lower than min vals
531 so we can see amplitude on the graph */
532 adj = (im->maxval - im->minval) * 0.1;
534 } else if (im->extra_flags & ALTAUTOSCALE_MAX) {
535 /* measure the amplitude of the function. Make sure that
536 graph boundaries are slightly higher than max vals
537 so we can see amplitude on the graph */
538 adj = (im->maxval - im->minval) * 0.1;
541 scaled_min = im->minval / im->magfact;
542 scaled_max = im->maxval / im->magfact;
544 for (i = 1; sensiblevalues[i] > 0; i++) {
545 if (sensiblevalues[i - 1] >= scaled_min &&
546 sensiblevalues[i] <= scaled_min)
547 im->minval = sensiblevalues[i] * (im->magfact);
549 if (-sensiblevalues[i - 1] <= scaled_min &&
550 -sensiblevalues[i] >= scaled_min)
551 im->minval = -sensiblevalues[i - 1] * (im->magfact);
553 if (sensiblevalues[i - 1] >= scaled_max &&
554 sensiblevalues[i] <= scaled_max)
555 im->maxval = sensiblevalues[i - 1] * (im->magfact);
557 if (-sensiblevalues[i - 1] <= scaled_max &&
558 -sensiblevalues[i] >= scaled_max)
559 im->maxval = -sensiblevalues[i] * (im->magfact);
563 /* adjust min and max to the grid definition if there is one */
564 im->minval = (double) im->ylabfact * im->ygridstep *
565 floor(im->minval / ((double) im->ylabfact * im->ygridstep));
566 im->maxval = (double) im->ylabfact * im->ygridstep *
567 ceil(im->maxval / ((double) im->ylabfact * im->ygridstep));
571 fprintf(stderr, "SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
572 im->minval, im->maxval, im->magfact);
580 if (isnan(im->minval) || isnan(im->maxval))
583 if (im->logarithmic) {
584 double ya, yb, ypix, ypixfrac;
585 double log10_range = log10(im->maxval) - log10(im->minval);
587 ya = pow((double) 10, floor(log10(im->minval)));
588 while (ya < im->minval)
591 return; /* don't have y=10^x gridline */
593 if (yb <= im->maxval) {
594 /* we have at least 2 y=10^x gridlines.
595 Make sure distance between them in pixels
596 are an integer by expanding im->maxval */
597 double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
598 double factor = y_pixel_delta / floor(y_pixel_delta);
599 double new_log10_range = factor * log10_range;
600 double new_ymax_log10 = log10(im->minval) + new_log10_range;
602 im->maxval = pow(10, new_ymax_log10);
603 ytr(im, DNAN); /* reset precalc */
604 log10_range = log10(im->maxval) - log10(im->minval);
606 /* make sure first y=10^x gridline is located on
607 integer pixel position by moving scale slightly
608 downwards (sub-pixel movement) */
609 ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
610 ypixfrac = ypix - floor(ypix);
611 if (ypixfrac > 0 && ypixfrac < 1) {
612 double yfrac = ypixfrac / im->ysize;
614 im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
615 im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
616 ytr(im, DNAN); /* reset precalc */
619 /* Make sure we have an integer pixel distance between
620 each minor gridline */
621 double ypos1 = ytr(im, im->minval);
622 double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
623 double y_pixel_delta = ypos1 - ypos2;
624 double factor = y_pixel_delta / floor(y_pixel_delta);
625 double new_range = factor * (im->maxval - im->minval);
626 double gridstep = im->ygrid_scale.gridstep;
627 double minor_y, minor_y_px, minor_y_px_frac;
629 if (im->maxval > 0.0)
630 im->maxval = im->minval + new_range;
632 im->minval = im->maxval - new_range;
633 ytr(im, DNAN); /* reset precalc */
634 /* make sure first minor gridline is on integer pixel y coord */
635 minor_y = gridstep * floor(im->minval / gridstep);
636 while (minor_y < im->minval)
638 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
639 minor_y_px_frac = minor_y_px - floor(minor_y_px);
640 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
641 double yfrac = minor_y_px_frac / im->ysize;
642 double range = im->maxval - im->minval;
644 im->minval = im->minval - yfrac * range;
645 im->maxval = im->maxval - yfrac * range;
646 ytr(im, DNAN); /* reset precalc */
648 calc_horizontal_grid(im); /* recalc with changed im->maxval */
652 /* reduce data reimplementation by Alex */
655 enum cf_en cf, /* which consolidation function ? */
656 unsigned long cur_step, /* step the data currently is in */
657 time_t *start, /* start, end and step as requested ... */
658 time_t *end, /* ... by the application will be ... */
659 unsigned long *step, /* ... adjusted to represent reality */
660 unsigned long *ds_cnt, /* number of data sources in file */
662 { /* two dimensional array containing the data */
663 int i, reduce_factor = ceil((double) (*step) / (double) cur_step);
664 unsigned long col, dst_row, row_cnt, start_offset, end_offset, skiprows =
666 rrd_value_t *srcptr, *dstptr;
668 (*step) = cur_step * reduce_factor; /* set new step size for reduced data */
671 row_cnt = ((*end) - (*start)) / cur_step;
677 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
678 row_cnt, reduce_factor, *start, *end, cur_step);
679 for (col = 0; col < row_cnt; col++) {
680 printf("time %10lu: ", *start + (col + 1) * cur_step);
681 for (i = 0; i < *ds_cnt; i++)
682 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
687 /* We have to combine [reduce_factor] rows of the source
688 ** into one row for the destination. Doing this we also
689 ** need to take care to combine the correct rows. First
690 ** alter the start and end time so that they are multiples
691 ** of the new step time. We cannot reduce the amount of
692 ** time so we have to move the end towards the future and
693 ** the start towards the past.
695 end_offset = (*end) % (*step);
696 start_offset = (*start) % (*step);
698 /* If there is a start offset (which cannot be more than
699 ** one destination row), skip the appropriate number of
700 ** source rows and one destination row. The appropriate
701 ** number is what we do know (start_offset/cur_step) of
702 ** the new interval (*step/cur_step aka reduce_factor).
705 printf("start_offset: %lu end_offset: %lu\n", start_offset, end_offset);
706 printf("row_cnt before: %lu\n", row_cnt);
709 (*start) = (*start) - start_offset;
710 skiprows = reduce_factor - start_offset / cur_step;
711 srcptr += skiprows * *ds_cnt;
712 for (col = 0; col < (*ds_cnt); col++)
717 printf("row_cnt between: %lu\n", row_cnt);
720 /* At the end we have some rows that are not going to be
721 ** used, the amount is end_offset/cur_step
724 (*end) = (*end) - end_offset + (*step);
725 skiprows = end_offset / cur_step;
729 printf("row_cnt after: %lu\n", row_cnt);
732 /* Sanity check: row_cnt should be multiple of reduce_factor */
733 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
735 if (row_cnt % reduce_factor) {
736 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
737 row_cnt, reduce_factor);
738 printf("BUG in reduce_data()\n");
742 /* Now combine reduce_factor intervals at a time
743 ** into one interval for the destination.
746 for (dst_row = 0; (long int) row_cnt >= reduce_factor; dst_row++) {
747 for (col = 0; col < (*ds_cnt); col++) {
748 rrd_value_t newval = DNAN;
749 unsigned long validval = 0;
751 for (i = 0; i < reduce_factor; i++) {
752 if (isnan(srcptr[i * (*ds_cnt) + col])) {
757 newval = srcptr[i * (*ds_cnt) + col];
766 newval += srcptr[i * (*ds_cnt) + col];
769 newval = min(newval, srcptr[i * (*ds_cnt) + col]);
772 /* an interval contains a failure if any subintervals contained a failure */
774 newval = max(newval, srcptr[i * (*ds_cnt) + col]);
777 newval = srcptr[i * (*ds_cnt) + col];
803 srcptr += (*ds_cnt) * reduce_factor;
804 row_cnt -= reduce_factor;
806 /* If we had to alter the endtime, we didn't have enough
807 ** source rows to fill the last row. Fill it with NaN.
810 for (col = 0; col < (*ds_cnt); col++)
813 row_cnt = ((*end) - (*start)) / *step;
815 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
816 row_cnt, *start, *end, *step);
817 for (col = 0; col < row_cnt; col++) {
818 printf("time %10lu: ", *start + (col + 1) * (*step));
819 for (i = 0; i < *ds_cnt; i++)
820 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
827 /* get the data required for the graphs from the
836 /* pull the data from the rrd files ... */
837 for (i = 0; i < (int) im->gdes_c; i++) {
838 /* only GF_DEF elements fetch data */
839 if (im->gdes[i].gf != GF_DEF)
843 /* do we have it already ? */
844 for (ii = 0; ii < i; ii++) {
845 if (im->gdes[ii].gf != GF_DEF)
847 if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
848 && (im->gdes[i].cf == im->gdes[ii].cf)
849 && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
850 && (im->gdes[i].start_orig == im->gdes[ii].start_orig)
851 && (im->gdes[i].end_orig == im->gdes[ii].end_orig)
852 && (im->gdes[i].step_orig == im->gdes[ii].step_orig)) {
853 /* OK, the data is already there.
854 ** Just copy the header portion
856 im->gdes[i].start = im->gdes[ii].start;
857 im->gdes[i].end = im->gdes[ii].end;
858 im->gdes[i].step = im->gdes[ii].step;
859 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
860 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
861 im->gdes[i].data = im->gdes[ii].data;
862 im->gdes[i].data_first = 0;
869 unsigned long ft_step = im->gdes[i].step; /* ft_step will record what we got from fetch */
870 const char *rrd_daemon;
873 if (im->gdes[i].daemon[0] != 0)
874 rrd_daemon = im->gdes[i].daemon;
876 rrd_daemon = im->daemon_addr;
878 /* "daemon" may be NULL. ENV_RRDCACHED_ADDRESS is evaluated in that
879 * case. If "daemon" holds the same value as in the previous
880 * iteration, no actual new connection is established - the
881 * existing connection is re-used. */
882 rrdc_connect (rrd_daemon);
884 /* If connecting was successfull, use the daemon to query the data.
885 * If there is no connection, for example because no daemon address
886 * was specified, (try to) use the local file directly. */
887 if (rrdc_is_connected (rrd_daemon))
889 status = rrdc_fetch (im->gdes[i].rrd,
890 cf_to_string (im->gdes[i].cf),
895 &im->gdes[i].ds_namv,
902 if ((rrd_fetch_fn(im->gdes[i].rrd,
908 &im->gdes[i].ds_namv,
909 &im->gdes[i].data)) == -1) {
913 im->gdes[i].data_first = 1;
915 /* must reduce to at least im->step
916 otherwhise we end up with more data than we can handle in the
917 chart and visibility of data will be random */
918 im->gdes[i].step = max(im->gdes[i].step,im->step);
919 if (ft_step < im->gdes[i].step) {
920 reduce_data(im->gdes[i].cf_reduce,
925 &im->gdes[i].ds_cnt, &im->gdes[i].data);
927 im->gdes[i].step = ft_step;
931 /* lets see if the required data source is really there */
932 for (ii = 0; ii < (int) im->gdes[i].ds_cnt; ii++) {
933 if (strcmp(im->gdes[i].ds_namv[ii], im->gdes[i].ds_nam) == 0) {
937 if (im->gdes[i].ds == -1) {
938 rrd_set_error("No DS called '%s' in '%s'",
939 im->gdes[i].ds_nam, im->gdes[i].rrd);
947 /* evaluate the expressions in the CDEF functions */
949 /*************************************************************
951 *************************************************************/
953 long find_var_wrapper(
957 return find_var((image_desc_t *) arg1, key);
960 /* find gdes containing var*/
967 for (ii = 0; ii < im->gdes_c - 1; ii++) {
968 if ((im->gdes[ii].gf == GF_DEF
969 || im->gdes[ii].gf == GF_VDEF || im->gdes[ii].gf == GF_CDEF)
970 && (strcmp(im->gdes[ii].vname, key) == 0)) {
977 /* find the greatest common divisor for all the numbers
978 in the 0 terminated num array */
985 for (i = 0; num[i + 1] != 0; i++) {
987 rest = num[i] % num[i + 1];
993 /* return i==0?num[i]:num[i-1]; */
997 /* run the rpn calculator on all the VDEF and CDEF arguments */
1004 long *steparray, rpi;
1007 rpnstack_t rpnstack;
1009 rpnstack_init(&rpnstack);
1011 for (gdi = 0; gdi < im->gdes_c; gdi++) {
1012 /* Look for GF_VDEF and GF_CDEF in the same loop,
1013 * so CDEFs can use VDEFs and vice versa
1015 switch (im->gdes[gdi].gf) {
1019 graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
1021 /* remove current shift */
1022 vdp->start -= vdp->shift;
1023 vdp->end -= vdp->shift;
1026 if (im->gdes[gdi].shidx >= 0)
1027 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
1030 vdp->shift = im->gdes[gdi].shval;
1032 /* normalize shift to multiple of consolidated step */
1033 vdp->shift = (vdp->shift / (long) vdp->step) * (long) vdp->step;
1036 vdp->start += vdp->shift;
1037 vdp->end += vdp->shift;
1041 /* A VDEF has no DS. This also signals other parts
1042 * of rrdtool that this is a VDEF value, not a CDEF.
1044 im->gdes[gdi].ds_cnt = 0;
1045 if (vdef_calc(im, gdi)) {
1046 rrd_set_error("Error processing VDEF '%s'",
1047 im->gdes[gdi].vname);
1048 rpnstack_free(&rpnstack);
1053 im->gdes[gdi].ds_cnt = 1;
1054 im->gdes[gdi].ds = 0;
1055 im->gdes[gdi].data_first = 1;
1056 im->gdes[gdi].start = 0;
1057 im->gdes[gdi].end = 0;
1062 /* Find the variables in the expression.
1063 * - VDEF variables are substituted by their values
1064 * and the opcode is changed into OP_NUMBER.
1065 * - CDEF variables are analized for their step size,
1066 * the lowest common denominator of all the step
1067 * sizes of the data sources involved is calculated
1068 * and the resulting number is the step size for the
1069 * resulting data source.
1071 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1072 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1073 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1074 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1076 if (im->gdes[ptr].ds_cnt == 0) { /* this is a VDEF data source */
1079 ("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
1080 im->gdes[gdi].vname, im->gdes[ptr].vname);
1081 printf("DEBUG: value from vdef is %f\n",
1082 im->gdes[ptr].vf.val);
1084 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
1085 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
1086 } else { /* normal variables and PREF(variables) */
1088 /* add one entry to the array that keeps track of the step sizes of the
1089 * data sources going into the CDEF. */
1091 (long*)rrd_realloc(steparray,
1093 1) * sizeof(*steparray))) == NULL) {
1094 rrd_set_error("realloc steparray");
1095 rpnstack_free(&rpnstack);
1099 steparray[stepcnt - 1] = im->gdes[ptr].step;
1101 /* adjust start and end of cdef (gdi) so
1102 * that it runs from the latest start point
1103 * to the earliest endpoint of any of the
1104 * rras involved (ptr)
1107 if (im->gdes[gdi].start < im->gdes[ptr].start)
1108 im->gdes[gdi].start = im->gdes[ptr].start;
1110 if (im->gdes[gdi].end == 0 ||
1111 im->gdes[gdi].end > im->gdes[ptr].end)
1112 im->gdes[gdi].end = im->gdes[ptr].end;
1114 /* store pointer to the first element of
1115 * the rra providing data for variable,
1116 * further save step size and data source
1119 im->gdes[gdi].rpnp[rpi].data =
1120 im->gdes[ptr].data + im->gdes[ptr].ds;
1121 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
1122 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
1124 /* backoff the *.data ptr; this is done so
1125 * rpncalc() function doesn't have to treat
1126 * the first case differently
1128 } /* if ds_cnt != 0 */
1129 } /* if OP_VARIABLE */
1130 } /* loop through all rpi */
1132 /* move the data pointers to the correct period */
1133 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1134 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1135 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1136 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1138 im->gdes[gdi].start - im->gdes[ptr].start;
1141 im->gdes[gdi].rpnp[rpi].data +=
1142 (diff / im->gdes[ptr].step) *
1143 im->gdes[ptr].ds_cnt;
1147 if (steparray == NULL) {
1148 rrd_set_error("rpn expressions without DEF"
1149 " or CDEF variables are not supported");
1150 rpnstack_free(&rpnstack);
1153 steparray[stepcnt] = 0;
1154 /* Now find the resulting step. All steps in all
1155 * used RRAs have to be visited
1157 im->gdes[gdi].step = lcd(steparray);
1159 if ((im->gdes[gdi].data = (rrd_value_t*)malloc(((im->gdes[gdi].end -
1160 im->gdes[gdi].start)
1161 / im->gdes[gdi].step)
1162 * sizeof(double))) == NULL) {
1163 rrd_set_error("malloc im->gdes[gdi].data");
1164 rpnstack_free(&rpnstack);
1168 /* Step through the new cdef results array and
1169 * calculate the values
1171 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
1172 now <= im->gdes[gdi].end; now += im->gdes[gdi].step) {
1173 rpnp_t *rpnp = im->gdes[gdi].rpnp;
1175 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
1176 * in this case we are advancing by timesteps;
1177 * we use the fact that time_t is a synonym for long
1179 if (rpn_calc(rpnp, &rpnstack, (long) now,
1180 im->gdes[gdi].data, ++dataidx) == -1) {
1181 /* rpn_calc sets the error string */
1182 rpnstack_free(&rpnstack);
1185 } /* enumerate over time steps within a CDEF */
1190 } /* enumerate over CDEFs */
1191 rpnstack_free(&rpnstack);
1195 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
1196 /* yes we are loosing precision by doing tos with floats instead of doubles
1197 but it seems more stable this way. */
1199 static int AlmostEqual2sComplement(
1205 int aInt = *(int *) &A;
1206 int bInt = *(int *) &B;
1209 /* Make sure maxUlps is non-negative and small enough that the
1210 default NAN won't compare as equal to anything. */
1212 /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
1214 /* Make aInt lexicographically ordered as a twos-complement int */
1217 aInt = 0x80000000l - aInt;
1219 /* Make bInt lexicographically ordered as a twos-complement int */
1222 bInt = 0x80000000l - bInt;
1224 intDiff = abs(aInt - bInt);
1226 if (intDiff <= maxUlps)
1232 /* massage data so, that we get one value for each x coordinate in the graph */
1237 double pixstep = (double) (im->end - im->start)
1238 / (double) im->xsize; /* how much time
1239 passes in one pixel */
1241 double minval = DNAN, maxval = DNAN;
1243 unsigned long gr_time;
1245 /* memory for the processed data */
1246 for (i = 0; i < im->gdes_c; i++) {
1247 if ((im->gdes[i].gf == GF_LINE)
1248 || (im->gdes[i].gf == GF_AREA)
1249 || (im->gdes[i].gf == GF_TICK)
1250 || (im->gdes[i].gf == GF_GRAD)
1252 if ((im->gdes[i].p_data = (rrd_value_t*)malloc((im->xsize + 1)
1253 * sizeof(rrd_value_t))) == NULL) {
1254 rrd_set_error("malloc data_proc");
1260 for (i = 0; i < im->xsize; i++) { /* for each pixel */
1263 gr_time = im->start + pixstep * i; /* time of the current step */
1266 for (ii = 0; ii < im->gdes_c; ii++) {
1269 switch (im->gdes[ii].gf) {
1274 if (!im->gdes[ii].stack)
1276 value = im->gdes[ii].yrule;
1277 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1278 /* The time of the data doesn't necessarily match
1279 ** the time of the graph. Beware.
1281 vidx = im->gdes[ii].vidx;
1282 if (im->gdes[vidx].gf == GF_VDEF) {
1283 value = im->gdes[vidx].vf.val;
1285 if (((long int) gr_time >=
1286 (long int) im->gdes[vidx].start)
1287 && ((long int) gr_time <
1288 (long int) im->gdes[vidx].end)) {
1289 value = im->gdes[vidx].data[(unsigned long)
1295 im->gdes[vidx].step)
1296 * im->gdes[vidx].ds_cnt +
1303 if (!isnan(value)) {
1305 im->gdes[ii].p_data[i] = paintval;
1306 /* GF_TICK: the data values are not
1307 ** relevant for min and max
1309 if (finite(paintval) && im->gdes[ii].gf != GF_TICK) {
1310 if ((isnan(minval) || paintval < minval) &&
1311 !(im->logarithmic && paintval <= 0.0))
1313 if (isnan(maxval) || paintval > maxval)
1317 im->gdes[ii].p_data[i] = DNAN;
1322 ("STACK should already be turned into LINE or AREA here");
1331 /* if min or max have not been asigned a value this is because
1332 there was no data in the graph ... this is not good ...
1333 lets set these to dummy values then ... */
1335 if (im->logarithmic) {
1336 if (isnan(minval) || isnan(maxval) || maxval <= 0) {
1337 minval = 0.0; /* catching this right away below */
1340 /* in logarithm mode, where minval is smaller or equal
1341 to 0 make the beast just way smaller than maxval */
1343 minval = maxval / 10e8;
1346 if (isnan(minval) || isnan(maxval)) {
1352 /* adjust min and max values given by the user */
1353 /* for logscale we add something on top */
1354 if (isnan(im->minval)
1355 || ((!im->rigid) && im->minval > minval)
1357 if (im->logarithmic)
1358 im->minval = minval / 2.0;
1360 im->minval = minval;
1362 if (isnan(im->maxval)
1363 || (!im->rigid && im->maxval < maxval)
1365 if (im->logarithmic)
1366 im->maxval = maxval * 2.0;
1368 im->maxval = maxval;
1371 /* make sure min is smaller than max */
1372 if (im->minval > im->maxval) {
1374 im->minval = 0.99 * im->maxval;
1376 im->minval = 1.01 * im->maxval;
1379 /* make sure min and max are not equal */
1380 if (AlmostEqual2sComplement(im->minval, im->maxval, 4)) {
1386 /* make sure min and max are not both zero */
1387 if (AlmostEqual2sComplement(im->maxval, 0, 4)) {
1394 static int find_first_weekday(void){
1395 static int first_weekday = -1;
1396 if (first_weekday == -1){
1397 #ifdef HAVE__NL_TIME_WEEK_1STDAY
1398 /* according to http://sourceware.org/ml/libc-locales/2009-q1/msg00011.html */
1399 long week_1stday_l = (long) nl_langinfo (_NL_TIME_WEEK_1STDAY);
1400 if (week_1stday_l == 19971130) first_weekday = 0; /* Sun */
1401 else if (week_1stday_l == 19971201) first_weekday = 1; /* Mon */
1402 else first_weekday = 1; /* we go for a monday default */
1407 return first_weekday;
1410 /* identify the point where the first gridline, label ... gets placed */
1412 time_t find_first_time(
1413 time_t start, /* what is the initial time */
1414 enum tmt_en baseint, /* what is the basic interval */
1415 long basestep /* how many if these do we jump a time */
1420 localtime_r(&start, &tm);
1424 tm. tm_sec -= tm.tm_sec % basestep;
1429 tm. tm_min -= tm.tm_min % basestep;
1435 tm. tm_hour -= tm.tm_hour % basestep;
1439 /* we do NOT look at the basestep for this ... */
1446 /* we do NOT look at the basestep for this ... */
1450 tm. tm_mday -= tm.tm_wday - find_first_weekday();
1452 if (tm.tm_wday == 0 && find_first_weekday() > 0)
1453 tm. tm_mday -= 7; /* we want the *previous* week */
1461 tm. tm_mon -= tm.tm_mon % basestep;
1472 tm.tm_year + 1900) %basestep;
1478 /* identify the point where the next gridline, label ... gets placed */
1479 time_t find_next_time(
1480 time_t current, /* what is the initial time */
1481 enum tmt_en baseint, /* what is the basic interval */
1482 long basestep /* how many if these do we jump a time */
1488 localtime_r(¤t, &tm);
1492 case TMT_SECOND: limit = 7200; break;
1493 case TMT_MINUTE: limit = 120; break;
1494 case TMT_HOUR: limit = 2; break;
1495 default: limit = 2; break;
1500 tm. tm_sec += basestep;
1504 tm. tm_min += basestep;
1508 tm. tm_hour += basestep;
1512 tm. tm_mday += basestep;
1516 tm. tm_mday += 7 * basestep;
1520 tm. tm_mon += basestep;
1524 tm. tm_year += basestep;
1526 madetime = mktime(&tm);
1527 } while (madetime == -1 && limit-- >= 0); /* this is necessary to skip impossible times
1528 like the daylight saving time skips */
1534 /* calculate values required for PRINT and GPRINT functions */
1539 long i, ii, validsteps;
1542 int graphelement = 0;
1545 double magfact = -1;
1550 /* wow initializing tmvdef is quite a task :-) */
1551 time_t now = time(NULL);
1553 localtime_r(&now, &tmvdef);
1554 for (i = 0; i < im->gdes_c; i++) {
1555 vidx = im->gdes[i].vidx;
1556 switch (im->gdes[i].gf) {
1559 /* PRINT and GPRINT can now print VDEF generated values.
1560 * There's no need to do any calculations on them as these
1561 * calculations were already made.
1563 if (im->gdes[vidx].gf == GF_VDEF) { /* simply use vals */
1564 printval = im->gdes[vidx].vf.val;
1565 localtime_r(&im->gdes[vidx].vf.when, &tmvdef);
1566 } else { /* need to calculate max,min,avg etcetera */
1567 max_ii = ((im->gdes[vidx].end - im->gdes[vidx].start)
1568 / im->gdes[vidx].step * im->gdes[vidx].ds_cnt);
1571 for (ii = im->gdes[vidx].ds;
1572 ii < max_ii; ii += im->gdes[vidx].ds_cnt) {
1573 if (!finite(im->gdes[vidx].data[ii]))
1575 if (isnan(printval)) {
1576 printval = im->gdes[vidx].data[ii];
1581 switch (im->gdes[i].cf) {
1585 case CF_DEVSEASONAL:
1589 printval += im->gdes[vidx].data[ii];
1592 printval = min(printval, im->gdes[vidx].data[ii]);
1596 printval = max(printval, im->gdes[vidx].data[ii]);
1599 printval = im->gdes[vidx].data[ii];
1602 if (im->gdes[i].cf == CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1603 if (validsteps > 1) {
1604 printval = (printval / validsteps);
1607 } /* prepare printval */
1609 if (!im->gdes[i].strftm && (percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
1610 /* Magfact is set to -1 upon entry to print_calc. If it
1611 * is still less than 0, then we need to run auto_scale.
1612 * Otherwise, put the value into the correct units. If
1613 * the value is 0, then do not set the symbol or magnification
1614 * so next the calculation will be performed again. */
1615 if (magfact < 0.0) {
1616 auto_scale(im, &printval, &si_symb, &magfact);
1617 if (printval == 0.0)
1620 printval /= magfact;
1622 *(++percent_s) = 's';
1623 } else if (!im->gdes[i].strftm && strstr(im->gdes[i].format, "%s") != NULL) {
1624 auto_scale(im, &printval, &si_symb, &magfact);
1627 if (im->gdes[i].gf == GF_PRINT) {
1628 rrd_infoval_t prline;
1630 if (im->gdes[i].strftm) {
1631 prline.u_str = (char*)malloc((FMT_LEG_LEN + 2) * sizeof(char));
1632 if (im->gdes[vidx].vf.never == 1) {
1633 time_clean(prline.u_str, im->gdes[i].format);
1635 strftime(prline.u_str,
1636 FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1638 } else if (bad_format(im->gdes[i].format)) {
1640 ("bad format for PRINT in '%s'", im->gdes[i].format);
1644 sprintf_alloc(im->gdes[i].format, printval, si_symb);
1648 ("print[%ld]", prline_cnt++), RD_I_STR, prline);
1653 if (im->gdes[i].strftm) {
1654 if (im->gdes[vidx].vf.never == 1) {
1655 time_clean(im->gdes[i].legend, im->gdes[i].format);
1657 strftime(im->gdes[i].legend,
1658 FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1661 if (bad_format(im->gdes[i].format)) {
1663 ("bad format for GPRINT in '%s'",
1664 im->gdes[i].format);
1667 #ifdef HAVE_SNPRINTF
1668 snprintf(im->gdes[i].legend,
1670 im->gdes[i].format, printval, si_symb);
1672 sprintf(im->gdes[i].legend,
1673 im->gdes[i].format, printval, si_symb);
1686 if (isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1687 im->gdes[i].yrule = im->gdes[vidx].vf.val;
1692 if (im->gdes[i].xrule == 0) { /* again ... the legend printer needs it */
1693 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1702 #ifdef WITH_PIECHART
1710 ("STACK should already be turned into LINE or AREA here");
1715 return graphelement;
1720 /* place legends with color spots */
1726 int interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1727 int border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1728 int fill = 0, fill_last;
1729 double legendwidth; // = im->ximg - 2 * border;
1731 double leg_x = border;
1732 int leg_y = 0; //im->yimg;
1733 int leg_y_prev = 0; // im->yimg;
1736 int i, ii, mark = 0;
1737 char default_txtalign = TXA_JUSTIFIED; /*default line orientation */
1740 char saved_legend[FMT_LEG_LEN + 5];
1746 legendwidth = im->legendwidth - 2 * border;
1750 if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
1751 if ((legspace = (int*)malloc(im->gdes_c * sizeof(int))) == NULL) {
1752 rrd_set_error("malloc for legspace");
1756 for (i = 0; i < im->gdes_c; i++) {
1757 char prt_fctn; /*special printfunctions */
1759 strcpy(saved_legend, im->gdes[i].legend);
1763 /* hide legends for rules which are not displayed */
1764 if (im->gdes[i].gf == GF_TEXTALIGN) {
1765 default_txtalign = im->gdes[i].txtalign;
1768 if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1769 if (im->gdes[i].gf == GF_HRULE
1770 && (im->gdes[i].yrule <
1771 im->minval || im->gdes[i].yrule > im->maxval))
1772 im->gdes[i].legend[0] = '\0';
1773 if (im->gdes[i].gf == GF_VRULE
1774 && (im->gdes[i].xrule <
1775 im->start || im->gdes[i].xrule > im->end))
1776 im->gdes[i].legend[0] = '\0';
1779 /* turn \\t into tab */
1780 while ((tab = strstr(im->gdes[i].legend, "\\t"))) {
1781 memmove(tab, tab + 1, strlen(tab));
1785 leg_cc = strlen(im->gdes[i].legend);
1786 /* is there a controle code at the end of the legend string ? */
1787 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\') {
1788 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1790 im->gdes[i].legend[leg_cc] = '\0';
1794 /* only valid control codes */
1795 if (prt_fctn != 'l' && prt_fctn != 'n' && /* a synonym for l */
1801 prt_fctn != 's' && prt_fctn != '\0' && prt_fctn != 'g') {
1804 ("Unknown control code at the end of '%s\\%c'",
1805 im->gdes[i].legend, prt_fctn);
1809 if (prt_fctn == 'n') {
1812 /* \. is a null operation to allow strings ending in \x */
1813 if (prt_fctn == '.') {
1817 /* remove exess space from the end of the legend for \g */
1818 while (prt_fctn == 'g' &&
1819 leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1821 im->gdes[i].legend[leg_cc] = '\0';
1826 /* no interleg space if string ends in \g */
1827 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1829 fill += legspace[i];
1832 gfx_get_text_width(im,
1838 im->tabwidth, im->gdes[i].legend);
1843 /* who said there was a special tag ... ? */
1844 if (prt_fctn == 'g') {
1848 if (prt_fctn == '\0') {
1849 if(calc_width && (fill > legendwidth)){
1852 if (i == im->gdes_c - 1 || fill > legendwidth) {
1853 /* just one legend item is left right or center */
1854 switch (default_txtalign) {
1869 /* is it time to place the legends ? */
1870 if (fill > legendwidth) {
1878 if (leg_c == 1 && prt_fctn == 'j') {
1883 if (prt_fctn != '\0') {
1885 if (leg_c >= 2 && prt_fctn == 'j') {
1886 glue = (double)(legendwidth - fill) / (double)(leg_c - 1);
1890 if (prt_fctn == 'c')
1891 leg_x = border + (double)(legendwidth - fill) / 2.0;
1892 if (prt_fctn == 'r')
1893 leg_x = legendwidth - fill + border;
1894 for (ii = mark; ii <= i; ii++) {
1895 if (im->gdes[ii].legend[0] == '\0')
1896 continue; /* skip empty legends */
1897 im->gdes[ii].leg_x = leg_x;
1898 im->gdes[ii].leg_y = leg_y + border;
1900 (double)gfx_get_text_width(im, leg_x,
1905 im->tabwidth, im->gdes[ii].legend)
1906 +(double)legspace[ii]
1910 if (leg_x > border || prt_fctn == 's')
1911 leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1912 if (prt_fctn == 's')
1913 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1914 if (prt_fctn == 'u')
1915 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size *1.8;
1917 if(calc_width && (fill > legendwidth)){
1926 strcpy(im->gdes[i].legend, saved_legend);
1931 im->legendwidth = legendwidth + 2 * border;
1934 im->legendheight = leg_y + border * 0.6;
1941 /* create a grid on the graph. it determines what to do
1942 from the values of xsize, start and end */
1944 /* the xaxis labels are determined from the number of seconds per pixel
1945 in the requested graph */
1947 int calc_horizontal_grid(
1955 int decimals, fractionals;
1957 im->ygrid_scale.labfact = 2;
1958 range = im->maxval - im->minval;
1959 scaledrange = range / im->magfact;
1960 /* does the scale of this graph make it impossible to put lines
1961 on it? If so, give up. */
1962 if (isnan(scaledrange)) {
1966 /* find grid spaceing */
1968 if (isnan(im->ygridstep)) {
1969 if (im->extra_flags & ALTYGRID) {
1970 /* find the value with max number of digits. Get number of digits */
1973 (max(fabs(im->maxval), fabs(im->minval)) *
1974 im->viewfactor / im->magfact));
1975 if (decimals <= 0) /* everything is small. make place for zero */
1977 im->ygrid_scale.gridstep =
1979 floor(log10(range * im->viewfactor / im->magfact))) /
1980 im->viewfactor * im->magfact;
1981 if (im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1982 im->ygrid_scale.gridstep = 0.1;
1983 /* should have at least 5 lines but no more then 15 */
1984 if (range / im->ygrid_scale.gridstep < 5
1985 && im->ygrid_scale.gridstep >= 30)
1986 im->ygrid_scale.gridstep /= 10;
1987 if (range / im->ygrid_scale.gridstep > 15)
1988 im->ygrid_scale.gridstep *= 10;
1989 if (range / im->ygrid_scale.gridstep > 5) {
1990 im->ygrid_scale.labfact = 1;
1991 if (range / im->ygrid_scale.gridstep > 8
1992 || im->ygrid_scale.gridstep <
1993 1.8 * im->text_prop[TEXT_PROP_AXIS].size)
1994 im->ygrid_scale.labfact = 2;
1996 im->ygrid_scale.gridstep /= 5;
1997 im->ygrid_scale.labfact = 5;
2001 (im->ygrid_scale.gridstep *
2002 (double) im->ygrid_scale.labfact * im->viewfactor /
2004 if (fractionals < 0) { /* small amplitude. */
2005 int len = decimals - fractionals + 1;
2007 if (im->unitslength < len + 2)
2008 im->unitslength = len + 2;
2009 sprintf(im->ygrid_scale.labfmt,
2011 -fractionals, (im->symbol != ' ' ? " %c" : ""));
2013 int len = decimals + 1;
2015 if (im->unitslength < len + 2)
2016 im->unitslength = len + 2;
2017 sprintf(im->ygrid_scale.labfmt,
2018 "%%%d.0f%s", len, (im->symbol != ' ' ? " %c" : ""));
2020 } else { /* classic rrd grid */
2021 for (i = 0; ylab[i].grid > 0; i++) {
2022 pixel = im->ysize / (scaledrange / ylab[i].grid);
2028 for (i = 0; i < 4; i++) {
2029 if (pixel * ylab[gridind].lfac[i] >=
2030 1.8 * im->text_prop[TEXT_PROP_AXIS].size) {
2031 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
2036 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
2039 im->ygrid_scale.gridstep = im->ygridstep;
2040 im->ygrid_scale.labfact = im->ylabfact;
2045 int draw_horizontal_grid(
2051 char graph_label[100];
2053 double X0 = im->xorigin;
2054 double X1 = im->xorigin + im->xsize;
2055 int sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
2056 int egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
2058 double second_axis_magfact = 0;
2059 char *second_axis_symb = "";
2062 im->ygrid_scale.gridstep /
2063 (double) im->magfact * (double) im->viewfactor;
2064 MaxY = scaledstep * (double) egrid;
2065 for (i = sgrid; i <= egrid; i++) {
2067 im->ygrid_scale.gridstep * i);
2069 im->ygrid_scale.gridstep * (i + 1));
2071 if (floor(Y0 + 0.5) >=
2072 im->yorigin - im->ysize && floor(Y0 + 0.5) <= im->yorigin) {
2073 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
2074 with the chosen settings. Add a label if required by settings, or if
2075 there is only one label so far and the next grid line is out of bounds. */
2076 if (i % im->ygrid_scale.labfact == 0
2078 && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
2079 if (im->symbol == ' ') {
2080 if (im->extra_flags & ALTYGRID) {
2081 sprintf(graph_label,
2082 im->ygrid_scale.labfmt,
2083 scaledstep * (double) i);
2086 sprintf(graph_label, "%4.1f",
2087 scaledstep * (double) i);
2089 sprintf(graph_label, "%4.0f",
2090 scaledstep * (double) i);
2094 char sisym = (i == 0 ? ' ' : im->symbol);
2096 if (im->extra_flags & ALTYGRID) {
2097 sprintf(graph_label,
2098 im->ygrid_scale.labfmt,
2099 scaledstep * (double) i, sisym);
2102 sprintf(graph_label, "%4.1f %c",
2103 scaledstep * (double) i, sisym);
2105 sprintf(graph_label, "%4.0f %c",
2106 scaledstep * (double) i, sisym);
2111 if (im->second_axis_scale != 0){
2112 char graph_label_right[100];
2113 double sval = im->ygrid_scale.gridstep*(double)i*im->second_axis_scale+im->second_axis_shift;
2114 if (im->second_axis_format[0] == '\0'){
2115 if (!second_axis_magfact){
2116 double dummy = im->ygrid_scale.gridstep*(double)(sgrid+egrid)/2.0*im->second_axis_scale+im->second_axis_shift;
2117 auto_scale(im,&dummy,&second_axis_symb,&second_axis_magfact);
2119 sval /= second_axis_magfact;
2122 sprintf(graph_label_right,"%5.1f %s",sval,second_axis_symb);
2124 sprintf(graph_label_right,"%5.0f %s",sval,second_axis_symb);
2128 sprintf(graph_label_right,im->second_axis_format,sval);
2132 im->graph_col[GRC_FONT],
2133 im->text_prop[TEXT_PROP_AXIS].font_desc,
2134 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2135 graph_label_right );
2141 text_prop[TEXT_PROP_AXIS].
2143 im->graph_col[GRC_FONT],
2145 text_prop[TEXT_PROP_AXIS].
2148 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2149 gfx_line(im, X0 - 2, Y0, X0, Y0,
2150 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2151 gfx_line(im, X1, Y0, X1 + 2, Y0,
2152 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2153 gfx_dashed_line(im, X0 - 2, Y0,
2159 im->grid_dash_on, im->grid_dash_off);
2160 } else if (!(im->extra_flags & NOMINOR)) {
2163 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2164 gfx_line(im, X1, Y0, X1 + 2, Y0,
2165 GRIDWIDTH, im->graph_col[GRC_GRID]);
2166 gfx_dashed_line(im, X0 - 1, Y0,
2170 graph_col[GRC_GRID],
2171 im->grid_dash_on, im->grid_dash_off);
2178 /* this is frexp for base 10 */
2189 iexp = floor(log((double)fabs(x)) / log((double)10));
2190 mnt = x / pow(10.0, iexp);
2193 mnt = x / pow(10.0, iexp);
2200 /* logaritmic horizontal grid */
2201 int horizontal_log_grid(
2205 double yloglab[][10] = {
2207 1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0,
2209 1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0,
2211 1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0,
2228 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} /* last line */
2230 int i, j, val_exp, min_exp;
2231 double nex; /* number of decades in data */
2232 double logscale; /* scale in logarithmic space */
2233 int exfrac = 1; /* decade spacing */
2234 int mid = -1; /* row in yloglab for major grid */
2235 double mspac; /* smallest major grid spacing (pixels) */
2236 int flab; /* first value in yloglab to use */
2237 double value, tmp, pre_value;
2239 char graph_label[100];
2241 nex = log10(im->maxval / im->minval);
2242 logscale = im->ysize / nex;
2243 /* major spacing for data with high dynamic range */
2244 while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2251 /* major spacing for less dynamic data */
2253 /* search best row in yloglab */
2255 for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2256 mspac = logscale * log10(10.0 / yloglab[mid][i]);
2259 2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
2262 /* find first value in yloglab */
2264 yloglab[mid][flab] < 10
2265 && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2266 if (yloglab[mid][flab] == 10.0) {
2271 if (val_exp % exfrac)
2272 val_exp += abs(-val_exp % exfrac);
2274 X1 = im->xorigin + im->xsize;
2279 value = yloglab[mid][flab] * pow(10.0, val_exp);
2280 if (AlmostEqual2sComplement(value, pre_value, 4))
2281 break; /* it seems we are not converging */
2283 Y0 = ytr(im, value);
2284 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2286 /* major grid line */
2288 X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2289 gfx_line(im, X1, Y0, X1 + 2, Y0,
2290 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2291 gfx_dashed_line(im, X0 - 2, Y0,
2296 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2298 if (im->extra_flags & FORCE_UNITS_SI) {
2303 scale = floor(val_exp / 3.0);
2305 pvalue = pow(10.0, val_exp % 3);
2307 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2308 pvalue *= yloglab[mid][flab];
2309 if (((scale + si_symbcenter) < (int) sizeof(si_symbol))
2310 && ((scale + si_symbcenter) >= 0))
2311 symbol = si_symbol[scale + si_symbcenter];
2314 sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2316 sprintf(graph_label, "%3.0e", value);
2318 if (im->second_axis_scale != 0){
2319 char graph_label_right[100];
2320 double sval = value*im->second_axis_scale+im->second_axis_shift;
2321 if (im->second_axis_format[0] == '\0'){
2322 if (im->extra_flags & FORCE_UNITS_SI) {
2325 auto_scale(im,&sval,&symb,&mfac);
2326 sprintf(graph_label_right,"%4.0f %s", sval,symb);
2329 sprintf(graph_label_right,"%3.0e", sval);
2333 sprintf(graph_label_right,im->second_axis_format,sval,"");
2338 im->graph_col[GRC_FONT],
2339 im->text_prop[TEXT_PROP_AXIS].font_desc,
2340 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2341 graph_label_right );
2347 text_prop[TEXT_PROP_AXIS].
2349 im->graph_col[GRC_FONT],
2351 text_prop[TEXT_PROP_AXIS].
2354 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2356 if (mid < 4 && exfrac == 1) {
2357 /* find first and last minor line behind current major line
2358 * i is the first line and j tha last */
2360 min_exp = val_exp - 1;
2361 for (i = 1; yloglab[mid][i] < 10.0; i++);
2362 i = yloglab[mid][i - 1] + 1;
2366 i = yloglab[mid][flab - 1] + 1;
2367 j = yloglab[mid][flab];
2370 /* draw minor lines below current major line */
2371 for (; i < j; i++) {
2373 value = i * pow(10.0, min_exp);
2374 if (value < im->minval)
2376 Y0 = ytr(im, value);
2377 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2382 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2383 gfx_line(im, X1, Y0, X1 + 2, Y0,
2384 GRIDWIDTH, im->graph_col[GRC_GRID]);
2385 gfx_dashed_line(im, X0 - 1, Y0,
2389 graph_col[GRC_GRID],
2390 im->grid_dash_on, im->grid_dash_off);
2392 } else if (exfrac > 1) {
2393 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2394 value = pow(10.0, i);
2395 if (value < im->minval)
2397 Y0 = ytr(im, value);
2398 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2403 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2404 gfx_line(im, X1, Y0, X1 + 2, Y0,
2405 GRIDWIDTH, im->graph_col[GRC_GRID]);
2406 gfx_dashed_line(im, X0 - 1, Y0,
2410 graph_col[GRC_GRID],
2411 im->grid_dash_on, im->grid_dash_off);
2416 if (yloglab[mid][++flab] == 10.0) {
2422 /* draw minor lines after highest major line */
2423 if (mid < 4 && exfrac == 1) {
2424 /* find first and last minor line below current major line
2425 * i is the first line and j tha last */
2427 min_exp = val_exp - 1;
2428 for (i = 1; yloglab[mid][i] < 10.0; i++);
2429 i = yloglab[mid][i - 1] + 1;
2433 i = yloglab[mid][flab - 1] + 1;
2434 j = yloglab[mid][flab];
2437 /* draw minor lines below current major line */
2438 for (; i < j; i++) {
2440 value = i * pow(10.0, min_exp);
2441 if (value < im->minval)
2443 Y0 = ytr(im, value);
2444 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2448 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2449 gfx_line(im, X1, Y0, X1 + 2, Y0,
2450 GRIDWIDTH, im->graph_col[GRC_GRID]);
2451 gfx_dashed_line(im, X0 - 1, Y0,
2455 graph_col[GRC_GRID],
2456 im->grid_dash_on, im->grid_dash_off);
2459 /* fancy minor gridlines */
2460 else if (exfrac > 1) {
2461 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2462 value = pow(10.0, i);
2463 if (value < im->minval)
2465 Y0 = ytr(im, value);
2466 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2470 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2471 gfx_line(im, X1, Y0, X1 + 2, Y0,
2472 GRIDWIDTH, im->graph_col[GRC_GRID]);
2473 gfx_dashed_line(im, X0 - 1, Y0,
2477 graph_col[GRC_GRID],
2478 im->grid_dash_on, im->grid_dash_off);
2489 int xlab_sel; /* which sort of label and grid ? */
2490 time_t ti, tilab, timajor;
2492 char graph_label[100];
2493 double X0, Y0, Y1; /* points for filled graph and more */
2496 /* the type of time grid is determined by finding
2497 the number of seconds per pixel in the graph */
2498 if (im->xlab_user.minsec == -1) {
2499 factor = (im->end - im->start) / im->xsize;
2501 while (xlab[xlab_sel + 1].minsec !=
2502 -1 && xlab[xlab_sel + 1].minsec <= factor) {
2504 } /* pick the last one */
2505 while (xlab[xlab_sel - 1].minsec ==
2506 xlab[xlab_sel].minsec
2507 && xlab[xlab_sel].length > (im->end - im->start)) {
2509 } /* go back to the smallest size */
2510 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2511 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2512 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2513 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2514 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2515 im->xlab_user.labst = xlab[xlab_sel].labst;
2516 im->xlab_user.precis = xlab[xlab_sel].precis;
2517 im->xlab_user.stst = xlab[xlab_sel].stst;
2520 /* y coords are the same for every line ... */
2522 Y1 = im->yorigin - im->ysize;
2523 /* paint the minor grid */
2524 if (!(im->extra_flags & NOMINOR)) {
2525 for (ti = find_first_time(im->start,
2533 find_first_time(im->start,
2538 ti < im->end && ti != -1;
2540 find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2542 /* are we inside the graph ? */
2543 if (ti < im->start || ti > im->end)
2545 while (timajor < ti && timajor != -1) {
2546 timajor = find_next_time(timajor,
2549 mgridtm, im->xlab_user.mgridst);
2551 if (timajor == -1) break; /* fail in case of problems with time increments */
2553 continue; /* skip as falls on major grid line */
2555 gfx_line(im, X0, Y1 - 2, X0, Y1,
2556 GRIDWIDTH, im->graph_col[GRC_GRID]);
2557 gfx_line(im, X0, Y0, X0, Y0 + 2,
2558 GRIDWIDTH, im->graph_col[GRC_GRID]);
2559 gfx_dashed_line(im, X0, Y0 + 1, X0,
2562 graph_col[GRC_GRID],
2563 im->grid_dash_on, im->grid_dash_off);
2567 /* paint the major grid */
2568 for (ti = find_first_time(im->start,
2575 ti < im->end && ti != -1;
2576 ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2578 /* are we inside the graph ? */
2579 if (ti < im->start || ti > im->end)
2582 gfx_line(im, X0, Y1 - 2, X0, Y1,
2583 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2584 gfx_line(im, X0, Y0, X0, Y0 + 3,
2585 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2586 gfx_dashed_line(im, X0, Y0 + 3, X0,
2590 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2592 /* paint the labels below the graph */
2594 find_first_time(im->start -
2603 im->xlab_user.precis / 2) && ti != -1;
2604 ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2606 tilab = ti + im->xlab_user.precis / 2; /* correct time for the label */
2607 /* are we inside the graph ? */
2608 if (tilab < im->start || tilab > im->end)
2611 localtime_r(&tilab, &tm);
2612 strftime(graph_label, 99, im->xlab_user.stst, &tm);
2614 # error "your libc has no strftime I guess we'll abort the exercise here."
2619 im->graph_col[GRC_FONT],
2621 text_prop[TEXT_PROP_AXIS].
2624 GFX_H_CENTER, GFX_V_TOP, graph_label);
2633 /* draw x and y axis */
2634 /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2635 im->xorigin+im->xsize,im->yorigin-im->ysize,
2636 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2638 gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2639 im->xorigin+im->xsize,im->yorigin-im->ysize,
2640 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2642 gfx_line(im, im->xorigin - 4,
2644 im->xorigin + im->xsize +
2645 4, im->yorigin, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2646 gfx_line(im, im->xorigin,
2649 im->yorigin - im->ysize -
2650 4, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2651 /* arrow for X and Y axis direction */
2652 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 */
2653 im->graph_col[GRC_ARROW]);
2655 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 */
2656 im->graph_col[GRC_ARROW]);
2658 if (im->second_axis_scale != 0){
2659 gfx_line ( im, im->xorigin+im->xsize,im->yorigin+4,
2660 im->xorigin+im->xsize,im->yorigin-im->ysize-4,
2661 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2663 im->xorigin+im->xsize-2, im->yorigin-im->ysize-2,
2664 im->xorigin+im->xsize+3, im->yorigin-im->ysize-2,
2665 im->xorigin+im->xsize, im->yorigin-im->ysize-7, /* LINEOFFSET */
2666 im->graph_col[GRC_ARROW]);
2677 double X0, Y0; /* points for filled graph and more */
2678 struct gfx_color_t water_color;
2680 if (im->draw_3d_border > 0) {
2681 /* draw 3d border */
2682 i = im->draw_3d_border;
2683 gfx_new_area(im, 0, im->yimg,
2684 i, im->yimg - i, i, i, im->graph_col[GRC_SHADEA]);
2685 gfx_add_point(im, im->ximg - i, i);
2686 gfx_add_point(im, im->ximg, 0);
2687 gfx_add_point(im, 0, 0);
2689 gfx_new_area(im, i, im->yimg - i,
2691 im->yimg - i, im->ximg - i, i, im->graph_col[GRC_SHADEB]);
2692 gfx_add_point(im, im->ximg, 0);
2693 gfx_add_point(im, im->ximg, im->yimg);
2694 gfx_add_point(im, 0, im->yimg);
2697 if (im->draw_x_grid == 1)
2699 if (im->draw_y_grid == 1) {
2700 if (im->logarithmic) {
2701 res = horizontal_log_grid(im);
2703 res = draw_horizontal_grid(im);
2706 /* dont draw horizontal grid if there is no min and max val */
2708 char *nodata = "No Data found";
2710 gfx_text(im, im->ximg / 2,
2713 im->graph_col[GRC_FONT],
2715 text_prop[TEXT_PROP_AXIS].
2718 GFX_H_CENTER, GFX_V_CENTER, nodata);
2722 /* yaxis unit description */
2723 if (im->ylegend[0] != '\0'){
2725 im->xOriginLegendY+10,
2727 im->graph_col[GRC_FONT],
2729 text_prop[TEXT_PROP_UNIT].
2732 RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2735 if (im->second_axis_legend[0] != '\0'){
2737 im->xOriginLegendY2+10,
2738 im->yOriginLegendY2,
2739 im->graph_col[GRC_FONT],
2740 im->text_prop[TEXT_PROP_UNIT].font_desc,
2742 RRDGRAPH_YLEGEND_ANGLE,
2743 GFX_H_CENTER, GFX_V_CENTER,
2744 im->second_axis_legend);
2749 im->xOriginTitle, im->yOriginTitle+6,
2750 im->graph_col[GRC_FONT],
2752 text_prop[TEXT_PROP_TITLE].
2754 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP, im->title);
2755 /* rrdtool 'logo' */
2756 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
2757 water_color = im->graph_col[GRC_FONT];
2758 water_color.alpha = 0.3;
2759 double xpos = im->legendposition == EAST ? im->xOriginLegendY : im->ximg - 4;
2760 gfx_text(im, xpos, 5,
2763 text_prop[TEXT_PROP_WATERMARK].
2764 font_desc, im->tabwidth,
2765 -90, GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2767 /* graph watermark */
2768 if (im->watermark[0] != '\0') {
2769 water_color = im->graph_col[GRC_FONT];
2770 water_color.alpha = 0.3;
2772 im->ximg / 2, im->yimg - 6,
2775 text_prop[TEXT_PROP_WATERMARK].
2776 font_desc, im->tabwidth, 0,
2777 GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2781 if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
2782 for (i = 0; i < im->gdes_c; i++) {
2783 if (im->gdes[i].legend[0] == '\0')
2785 /* im->gdes[i].leg_y is the bottom of the legend */
2786 X0 = im->xOriginLegend + im->gdes[i].leg_x;
2787 Y0 = im->legenddirection == TOP_DOWN ? im->yOriginLegend + im->gdes[i].leg_y : im->yOriginLegend + im->legendheight - im->gdes[i].leg_y;
2788 gfx_text(im, X0, Y0,
2789 im->graph_col[GRC_FONT],
2792 [TEXT_PROP_LEGEND].font_desc,
2794 GFX_H_LEFT, GFX_V_BOTTOM, im->gdes[i].legend);
2795 /* The legend for GRAPH items starts with "M " to have
2796 enough space for the box */
2797 if (im->gdes[i].gf != GF_PRINT &&
2798 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2802 boxH = gfx_get_text_width(im, 0,
2807 im->tabwidth, "o") * 1.2;
2809 /* shift the box up a bit */
2812 if (im->dynamic_labels && im->gdes[i].gf == GF_HRULE) { /* [-] */
2814 cairo_new_path(im->cr);
2815 cairo_set_line_width(im->cr, 1.0);
2818 X0 + boxH, Y0 - boxV / 2,
2819 1.0, im->gdes[i].col);
2821 } else if (im->dynamic_labels && im->gdes[i].gf == GF_VRULE) { /* [|] */
2823 cairo_new_path(im->cr);
2824 cairo_set_line_width(im->cr, 1.0);
2827 X0 + boxH / 2, Y0 - boxV,
2828 1.0, im->gdes[i].col);
2830 } else if (im->dynamic_labels && im->gdes[i].gf == GF_LINE) { /* [/] */
2832 cairo_new_path(im->cr);
2833 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
2836 X0 + boxH, Y0 - boxV,
2837 im->gdes[i].linewidth, im->gdes[i].col);
2840 /* make sure transparent colors show up the same way as in the graph */
2843 X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2844 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2846 gfx_new_area(im, X0, Y0 - boxV, X0,
2847 Y0, X0 + boxH, Y0, im->gdes[i].col);
2848 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2851 cairo_new_path(im->cr);
2852 cairo_set_line_width(im->cr, 1.0);
2855 gfx_line_fit(im, &X0, &Y0);
2856 gfx_line_fit(im, &X1, &Y1);
2857 cairo_move_to(im->cr, X0, Y0);
2858 cairo_line_to(im->cr, X1, Y0);
2859 cairo_line_to(im->cr, X1, Y1);
2860 cairo_line_to(im->cr, X0, Y1);
2861 cairo_close_path(im->cr);
2862 cairo_set_source_rgba(im->cr,
2863 im->graph_col[GRC_FRAME].red,
2864 im->graph_col[GRC_FRAME].green,
2865 im->graph_col[GRC_FRAME].blue,
2866 im->graph_col[GRC_FRAME].alpha);
2868 if (im->gdes[i].dash) {
2869 /* make box borders in legend dashed if the graph is dashed */
2873 cairo_set_dash(im->cr, dashes, 1, 0.0);
2875 cairo_stroke(im->cr);
2876 cairo_restore(im->cr);
2883 /*****************************************************
2884 * lazy check make sure we rely need to create this graph
2885 *****************************************************/
2892 struct stat imgstat;
2895 return 0; /* no lazy option */
2896 if (strlen(im->graphfile) == 0)
2897 return 0; /* inmemory option */
2898 if (stat(im->graphfile, &imgstat) != 0)
2899 return 0; /* can't stat */
2900 /* one pixel in the existing graph is more then what we would
2902 if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2904 if ((fd = fopen(im->graphfile, "rb")) == NULL)
2905 return 0; /* the file does not exist */
2906 switch (im->imgformat) {
2908 size = PngSize(fd, &(im->ximg), &(im->yimg));
2918 int graph_size_location(
2923 /* The actual size of the image to draw is determined from
2924 ** several sources. The size given on the command line is
2925 ** the graph area but we need more as we have to draw labels
2926 ** and other things outside the graph area. If the option
2927 ** --full-size-mode is selected the size defines the total
2928 ** image size and the size available for the graph is
2932 /** +---+-----------------------------------+
2933 ** | y |...............graph title.........|
2934 ** | +---+-------------------------------+
2938 ** | s | x | main graph area |
2943 ** | l | b +-------------------------------+
2944 ** | e | l | x axis labels |
2945 ** +---+---+-------------------------------+
2946 ** |....................legends............|
2947 ** +---------------------------------------+
2949 ** +---------------------------------------+
2952 int Xvertical = 0, Xvertical2 = 0, Ytitle =
2953 0, Xylabel = 0, Xmain = 0, Ymain =
2954 0, Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2956 // no legends and no the shall be plotted it's easy
2957 if (im->extra_flags & ONLY_GRAPH) {
2959 im->ximg = im->xsize;
2960 im->yimg = im->ysize;
2961 im->yorigin = im->ysize;
2967 if(im->watermark[0] != '\0') {
2968 Ywatermark = im->text_prop[TEXT_PROP_WATERMARK].size * 2;
2971 // calculate the width of the left vertical legend
2972 if (im->ylegend[0] != '\0') {
2973 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2976 // calculate the width of the right vertical legend
2977 if (im->second_axis_legend[0] != '\0') {
2978 Xvertical2 = im->text_prop[TEXT_PROP_UNIT].size * 2;
2981 Xvertical2 = Xspacing;
2984 if (im->title[0] != '\0') {
2985 /* The title is placed "inbetween" two text lines so it
2986 ** automatically has some vertical spacing. The horizontal
2987 ** spacing is added here, on each side.
2989 /* if necessary, reduce the font size of the title until it fits the image width */
2990 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2993 // we have no title; get a little clearing from the top
2998 if (im->draw_x_grid) {
2999 // calculate the height of the horizontal labelling
3000 Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
3002 if (im->draw_y_grid || im->forceleftspace) {
3003 // calculate the width of the vertical labelling
3005 gfx_get_text_width(im, 0,
3006 im->text_prop[TEXT_PROP_AXIS].font_desc,
3007 im->tabwidth, "0") * im->unitslength;
3011 // add some space to the labelling
3012 Xylabel += Xspacing;
3014 /* If the legend is printed besides the graph the width has to be
3015 ** calculated first. Placing the legend north or south of the
3016 ** graph requires the width calculation first, so the legend is
3017 ** skipped for the moment.
3019 im->legendheight = 0;
3020 im->legendwidth = 0;
3021 if (!(im->extra_flags & NOLEGEND)) {
3022 if(im->legendposition == WEST || im->legendposition == EAST){
3023 if (leg_place(im, 1) == -1){
3029 if (im->extra_flags & FULL_SIZE_MODE) {
3031 /* The actual size of the image to draw has been determined by the user.
3032 ** The graph area is the space remaining after accounting for the legend,
3033 ** the watermark, the axis labels, and the title.
3035 im->ximg = im->xsize;
3036 im->yimg = im->ysize;
3040 /* Now calculate the total size. Insert some spacing where
3041 desired. im->xorigin and im->yorigin need to correspond
3042 with the lower left corner of the main graph area or, if
3043 this one is not set, the imaginary box surrounding the
3045 /* Initial size calculation for the main graph area */
3047 Xmain -= Xylabel;// + Xspacing;
3048 if((im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
3049 Xmain -= im->legendwidth;// + Xspacing;
3051 if (im->second_axis_scale != 0){
3054 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3058 Xmain -= Xvertical + Xvertical2;
3060 /* limit the remaining space to 0 */
3066 /* Putting the legend north or south, the height can now be calculated */
3067 if (!(im->extra_flags & NOLEGEND)) {
3068 if(im->legendposition == NORTH || im->legendposition == SOUTH){
3069 im->legendwidth = im->ximg;
3070 if (leg_place(im, 0) == -1){
3076 if( (im->legendposition == NORTH || im->legendposition == SOUTH) && !(im->extra_flags & NOLEGEND) ){
3077 Ymain -= Yxlabel + im->legendheight;
3083 /* reserve space for the title *or* some padding above the graph */
3086 /* reserve space for padding below the graph */
3087 if (im->extra_flags & NOLEGEND) {
3088 Ymain -= 0.5*Yspacing;
3091 if (im->watermark[0] != '\0') {
3092 Ymain -= Ywatermark;
3094 /* limit the remaining height to 0 */
3099 } else { /* dimension options -width and -height refer to the dimensions of the main graph area */
3101 /* The actual size of the image to draw is determined from
3102 ** several sources. The size given on the command line is
3103 ** the graph area but we need more as we have to draw labels
3104 ** and other things outside the graph area.
3108 Xmain = im->xsize; // + Xspacing;
3112 im->ximg = Xmain + Xylabel;
3113 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3114 im->ximg += Xspacing;
3117 if( (im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
3118 im->ximg += im->legendwidth;// + Xspacing;
3120 if (im->second_axis_scale != 0){
3121 im->ximg += Xylabel;
3124 im->ximg += Xvertical + Xvertical2;
3126 if (!(im->extra_flags & NOLEGEND)) {
3127 if(im->legendposition == NORTH || im->legendposition == SOUTH){
3128 im->legendwidth = im->ximg;
3129 if (leg_place(im, 0) == -1){
3135 im->yimg = Ymain + Yxlabel;
3136 if( (im->legendposition == NORTH || im->legendposition == SOUTH) && !(im->extra_flags & NOLEGEND) ){
3137 im->yimg += im->legendheight;
3140 /* reserve space for the title *or* some padding above the graph */
3144 im->yimg += 1.5 * Yspacing;
3146 /* reserve space for padding below the graph */
3147 if (im->extra_flags & NOLEGEND) {
3148 im->yimg += 0.5*Yspacing;
3151 if (im->watermark[0] != '\0') {
3152 im->yimg += Ywatermark;
3157 /* In case of putting the legend in west or east position the first
3158 ** legend calculation might lead to wrong positions if some items
3159 ** are not aligned on the left hand side (e.g. centered) as the
3160 ** legendwidth wight have been increased after the item was placed.
3161 ** In this case the positions have to be recalculated.
3163 if (!(im->extra_flags & NOLEGEND)) {
3164 if(im->legendposition == WEST || im->legendposition == EAST){
3165 if (leg_place(im, 0) == -1){
3171 /* After calculating all dimensions
3172 ** it is now possible to calculate
3175 switch(im->legendposition){
3177 im->xOriginTitle = (im->ximg / 2);
3178 im->yOriginTitle = 0;
3180 im->xOriginLegend = 0;
3181 im->yOriginLegend = Ytitle;
3183 im->xOriginLegendY = 0;
3184 im->yOriginLegendY = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3186 im->xorigin = Xvertical + Xylabel;
3187 im->yorigin = Ytitle + im->legendheight + Ymain;
3189 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3190 if (im->second_axis_scale != 0){
3191 im->xOriginLegendY2 += Xylabel;
3193 im->yOriginLegendY2 = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3198 im->xOriginTitle = im->legendwidth + im->xsize / 2;
3199 im->yOriginTitle = 0;
3201 im->xOriginLegend = 0;
3202 im->yOriginLegend = Ytitle;
3204 im->xOriginLegendY = im->legendwidth;
3205 im->yOriginLegendY = Ytitle + (Ymain / 2);
3207 im->xorigin = im->legendwidth + Xvertical + Xylabel;
3208 im->yorigin = Ytitle + Ymain;
3210 im->xOriginLegendY2 = im->legendwidth + Xvertical + Xylabel + Xmain;
3211 if (im->second_axis_scale != 0){
3212 im->xOriginLegendY2 += Xylabel;
3214 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3219 im->xOriginTitle = im->ximg / 2;
3220 im->yOriginTitle = 0;
3222 im->xOriginLegend = 0;
3223 im->yOriginLegend = Ytitle + Ymain + Yxlabel;
3225 im->xOriginLegendY = 0;
3226 im->yOriginLegendY = Ytitle + (Ymain / 2);
3228 im->xorigin = Xvertical + Xylabel;
3229 im->yorigin = Ytitle + Ymain;
3231 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3232 if (im->second_axis_scale != 0){
3233 im->xOriginLegendY2 += Xylabel;
3235 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3240 im->xOriginTitle = im->xsize / 2;
3241 im->yOriginTitle = 0;
3243 im->xOriginLegend = Xvertical + Xylabel + Xmain + Xvertical2;
3244 if (im->second_axis_scale != 0){
3245 im->xOriginLegend += Xylabel;
3247 im->yOriginLegend = Ytitle;
3249 im->xOriginLegendY = 0;
3250 im->yOriginLegendY = Ytitle + (Ymain / 2);
3252 im->xorigin = Xvertical + Xylabel;
3253 im->yorigin = Ytitle + Ymain;
3255 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3256 if (im->second_axis_scale != 0){
3257 im->xOriginLegendY2 += Xylabel;
3259 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3261 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3262 im->xOriginTitle += Xspacing;
3263 im->xOriginLegend += Xspacing;
3264 im->xOriginLegendY += Xspacing;
3265 im->xorigin += Xspacing;
3266 im->xOriginLegendY2 += Xspacing;
3276 static cairo_status_t cairo_output(
3280 unsigned int length)
3282 image_desc_t *im = (image_desc_t*)closure;
3284 im->rendered_image =
3285 (unsigned char*)realloc(im->rendered_image, im->rendered_image_size + length);
3286 if (im->rendered_image == NULL)
3287 return CAIRO_STATUS_WRITE_ERROR;
3288 memcpy(im->rendered_image + im->rendered_image_size, data, length);
3289 im->rendered_image_size += length;
3290 return CAIRO_STATUS_SUCCESS;
3293 /* draw that picture thing ... */
3298 int lazy = lazy_check(im);
3299 double areazero = 0.0;
3300 graph_desc_t *lastgdes = NULL;
3303 // PangoFontMap *font_map = pango_cairo_font_map_get_default();
3305 /* pull the data from the rrd files ... */
3306 if (data_fetch(im) == -1)
3308 /* evaluate VDEF and CDEF operations ... */
3309 if (data_calc(im) == -1)
3311 /* calculate and PRINT and GPRINT definitions. We have to do it at
3312 * this point because it will affect the length of the legends
3313 * if there are no graph elements (i==0) we stop here ...
3314 * if we are lazy, try to quit ...
3320 /* if we want and can be lazy ... quit now */
3324 /**************************************************************
3325 *** Calculating sizes and locations became a bit confusing ***
3326 *** so I moved this into a separate function. ***
3327 **************************************************************/
3328 if (graph_size_location(im, i) == -1)
3331 info.u_cnt = im->xorigin;
3332 grinfo_push(im, sprintf_alloc("graph_left"), RD_I_CNT, info);
3333 info.u_cnt = im->yorigin - im->ysize;
3334 grinfo_push(im, sprintf_alloc("graph_top"), RD_I_CNT, info);
3335 info.u_cnt = im->xsize;
3336 grinfo_push(im, sprintf_alloc("graph_width"), RD_I_CNT, info);
3337 info.u_cnt = im->ysize;
3338 grinfo_push(im, sprintf_alloc("graph_height"), RD_I_CNT, info);
3339 info.u_cnt = im->ximg;
3340 grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
3341 info.u_cnt = im->yimg;
3342 grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
3343 info.u_cnt = im->start;
3344 grinfo_push(im, sprintf_alloc("graph_start"), RD_I_CNT, info);
3345 info.u_cnt = im->end;
3346 grinfo_push(im, sprintf_alloc("graph_end"), RD_I_CNT, info);
3348 /* if we want and can be lazy ... quit now */
3352 /* get actual drawing data and find min and max values */
3353 if (data_proc(im) == -1)
3355 if (!im->logarithmic) {
3359 /* identify si magnitude Kilo, Mega Giga ? */
3360 if (!im->rigid && !im->logarithmic)
3361 expand_range(im); /* make sure the upper and lower limit are
3364 info.u_val = im->minval;
3365 grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
3366 info.u_val = im->maxval;
3367 grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
3370 if (!calc_horizontal_grid(im))
3375 apply_gridfit(im); */
3376 /* the actual graph is created by going through the individual
3377 graph elements and then drawing them */
3378 cairo_surface_destroy(im->surface);
3379 switch (im->imgformat) {
3382 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3383 im->ximg * im->zoom,
3384 im->yimg * im->zoom);
3388 im->surface = strlen(im->graphfile)
3389 ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3390 im->yimg * im->zoom)
3391 : cairo_pdf_surface_create_for_stream
3392 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3396 im->surface = strlen(im->graphfile)
3398 cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3399 im->yimg * im->zoom)
3400 : cairo_ps_surface_create_for_stream
3401 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3405 im->surface = strlen(im->graphfile)
3407 cairo_svg_surface_create(im->
3409 im->ximg * im->zoom, im->yimg * im->zoom)
3410 : cairo_svg_surface_create_for_stream
3411 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3412 cairo_svg_surface_restrict_to_version
3413 (im->surface, CAIRO_SVG_VERSION_1_1);
3424 cairo_destroy(im->cr);
3425 im->cr = cairo_create(im->surface);
3426 cairo_set_antialias(im->cr, im->graph_antialias);
3427 cairo_scale(im->cr, im->zoom, im->zoom);
3428 // pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3429 gfx_new_area(im, 0, 0, 0, im->yimg,
3430 im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3431 gfx_add_point(im, im->ximg, 0);
3433 gfx_new_area(im, im->xorigin,
3436 im->xsize, im->yorigin,
3439 im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3440 gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3442 cairo_rectangle(im->cr, im->xorigin, im->yorigin - im->ysize - 1.0,
3443 im->xsize, im->ysize + 2.0);
3445 if (im->minval > 0.0)
3446 areazero = im->minval;
3447 if (im->maxval < 0.0)
3448 areazero = im->maxval;
3449 for (i = 0; i < im->gdes_c; i++) {
3450 switch (im->gdes[i].gf) {
3464 for (ii = 0; ii < im->xsize; ii++) {
3465 if (!isnan(im->gdes[i].p_data[ii])
3466 && im->gdes[i].p_data[ii] != 0.0) {
3467 if (im->gdes[i].yrule > 0) {
3474 im->ysize, 1.0, im->gdes[i].col);
3475 } else if (im->gdes[i].yrule < 0) {
3478 im->yorigin - im->ysize - 1.0,
3480 im->yorigin - im->ysize -
3483 im->ysize, 1.0, im->gdes[i].col);
3491 rrd_value_t diffval = im->maxval - im->minval;
3492 rrd_value_t maxlimit = im->maxval + 9 * diffval;
3493 rrd_value_t minlimit = im->minval - 9 * diffval;
3494 for (ii = 0; ii < im->xsize; ii++) {
3495 /* fix data points at oo and -oo */
3496 if (isinf(im->gdes[i].p_data[ii])) {
3497 if (im->gdes[i].p_data[ii] > 0) {
3498 im->gdes[i].p_data[ii] = im->maxval;
3500 im->gdes[i].p_data[ii] = im->minval;
3503 /* some versions of cairo go unstable when trying
3504 to draw way out of the canvas ... lets not even try */
3505 if (im->gdes[i].p_data[ii] > maxlimit) {
3506 im->gdes[i].p_data[ii] = maxlimit;
3508 if (im->gdes[i].p_data[ii] < minlimit) {
3509 im->gdes[i].p_data[ii] = minlimit;
3513 /* *******************************************************
3518 -------|--t-1--t--------------------------------
3520 if we know the value at time t was a then
3521 we draw a square from t-1 to t with the value a.
3523 ********************************************************* */
3524 if (im->gdes[i].col.alpha != 0.0) {
3525 /* GF_LINE and friend */
3526 if (im->gdes[i].gf == GF_LINE) {
3527 double last_y = 0.0;
3531 cairo_new_path(im->cr);
3532 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3533 if (im->gdes[i].dash) {
3534 cairo_set_dash(im->cr,
3535 im->gdes[i].p_dashes,
3536 im->gdes[i].ndash, im->gdes[i].offset);
3539 for (ii = 1; ii < im->xsize; ii++) {
3540 if (isnan(im->gdes[i].p_data[ii])
3541 || (im->slopemode == 1
3542 && isnan(im->gdes[i].p_data[ii - 1]))) {
3547 last_y = ytr(im, im->gdes[i].p_data[ii]);
3548 if (im->slopemode == 0) {
3549 double x = ii - 1 + im->xorigin;
3552 gfx_line_fit(im, &x, &y);
3553 cairo_move_to(im->cr, x, y);
3554 x = ii + im->xorigin;
3556 gfx_line_fit(im, &x, &y);
3557 cairo_line_to(im->cr, x, y);
3559 double x = ii - 1 + im->xorigin;
3561 ytr(im, im->gdes[i].p_data[ii - 1]);
3562 gfx_line_fit(im, &x, &y);
3563 cairo_move_to(im->cr, x, y);
3564 x = ii + im->xorigin;
3566 gfx_line_fit(im, &x, &y);
3567 cairo_line_to(im->cr, x, y);
3571 double x1 = ii + im->xorigin;
3572 double y1 = ytr(im, im->gdes[i].p_data[ii]);
3574 if (im->slopemode == 0
3575 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3576 double x = ii - 1 + im->xorigin;
3579 gfx_line_fit(im, &x, &y);
3580 cairo_line_to(im->cr, x, y);
3583 gfx_line_fit(im, &x1, &y1);
3584 cairo_line_to(im->cr, x1, y1);
3587 cairo_set_source_rgba(im->cr,
3593 col.blue, im->gdes[i].col.alpha);
3594 cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3595 cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3596 cairo_stroke(im->cr);
3597 cairo_restore(im->cr);
3603 (double *) malloc(sizeof(double) * im->xsize * 2);
3605 (double *) malloc(sizeof(double) * im->xsize * 2);
3607 (double *) malloc(sizeof(double) * im->xsize * 2);
3609 (double *) malloc(sizeof(double) * im->xsize * 2);
3612 for (ii = 0; ii <= im->xsize; ii++) {
3615 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3621 AlmostEqual2sComplement(foreY
3625 AlmostEqual2sComplement(foreY
3631 if (im->gdes[i].gf != GF_GRAD) {
3636 foreY[cntI], im->gdes[i].col);
3638 lastx = foreX[cntI];
3639 lasty = foreY[cntI];
3641 while (cntI < idxI) {
3646 AlmostEqual2sComplement(foreY
3650 AlmostEqual2sComplement(foreY
3657 if (im->gdes[i].gf != GF_GRAD) {
3658 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3660 gfx_add_rect_fadey(im,
3662 foreX[cntI], foreY[cntI], lasty,
3665 im->gdes[i].gradheight
3667 lastx = foreX[cntI];
3668 lasty = foreY[cntI];
3671 if (im->gdes[i].gf != GF_GRAD) {
3672 gfx_add_point(im, backX[idxI], backY[idxI]);
3674 gfx_add_rect_fadey(im,
3676 backX[idxI], backY[idxI], lasty,
3679 im->gdes[i].gradheight);
3680 lastx = backX[idxI];
3681 lasty = backY[idxI];
3688 AlmostEqual2sComplement(backY
3692 AlmostEqual2sComplement(backY
3699 if (im->gdes[i].gf != GF_GRAD) {
3700 gfx_add_point(im, backX[idxI], backY[idxI]);
3702 gfx_add_rect_fadey(im,
3704 backX[idxI], backY[idxI], lasty,
3707 im->gdes[i].gradheight);
3708 lastx = backX[idxI];
3709 lasty = backY[idxI];
3714 if (im->gdes[i].gf != GF_GRAD)
3721 if (ii == im->xsize)
3723 if (im->slopemode == 0 && ii == 0) {
3726 if (isnan(im->gdes[i].p_data[ii])) {
3730 ytop = ytr(im, im->gdes[i].p_data[ii]);
3731 if (lastgdes && im->gdes[i].stack) {
3732 ybase = ytr(im, lastgdes->p_data[ii]);
3734 ybase = ytr(im, areazero);
3736 if (ybase == ytop) {
3742 double extra = ytop;
3747 if (im->slopemode == 0) {
3748 backY[++idxI] = ybase - 0.2;
3749 backX[idxI] = ii + im->xorigin - 1;
3750 foreY[idxI] = ytop + 0.2;
3751 foreX[idxI] = ii + im->xorigin - 1;
3753 backY[++idxI] = ybase - 0.2;
3754 backX[idxI] = ii + im->xorigin;
3755 foreY[idxI] = ytop + 0.2;
3756 foreX[idxI] = ii + im->xorigin;
3758 /* close up any remaining area */
3763 } /* else GF_LINE */
3765 /* if color != 0x0 */
3766 /* make sure we do not run into trouble when stacking on NaN */
3767 for (ii = 0; ii < im->xsize; ii++) {
3768 if (isnan(im->gdes[i].p_data[ii])) {
3769 if (lastgdes && (im->gdes[i].stack)) {
3770 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3772 im->gdes[i].p_data[ii] = areazero;
3776 lastgdes = &(im->gdes[i]);
3778 } /* GF_AREA, GF_LINE, GF_GRAD */
3781 ("STACK should already be turned into LINE or AREA here");
3786 cairo_reset_clip(im->cr);
3788 /* grid_paint also does the text */
3789 if (!(im->extra_flags & ONLY_GRAPH))
3791 if (!(im->extra_flags & ONLY_GRAPH))
3793 /* the RULES are the last thing to paint ... */
3794 for (i = 0; i < im->gdes_c; i++) {
3796 switch (im->gdes[i].gf) {
3798 if (im->gdes[i].yrule >= im->minval
3799 && im->gdes[i].yrule <= im->maxval) {
3801 if (im->gdes[i].dash) {
3802 cairo_set_dash(im->cr,
3803 im->gdes[i].p_dashes,
3804 im->gdes[i].ndash, im->gdes[i].offset);
3806 gfx_line(im, im->xorigin,
3807 ytr(im, im->gdes[i].yrule),
3808 im->xorigin + im->xsize,
3809 ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
3810 cairo_stroke(im->cr);
3811 cairo_restore(im->cr);
3815 if (im->gdes[i].xrule >= im->start
3816 && im->gdes[i].xrule <= im->end) {
3818 if (im->gdes[i].dash) {
3819 cairo_set_dash(im->cr,
3820 im->gdes[i].p_dashes,
3821 im->gdes[i].ndash, im->gdes[i].offset);
3824 xtr(im, im->gdes[i].xrule),
3825 im->yorigin, xtr(im,
3829 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3830 cairo_stroke(im->cr);
3831 cairo_restore(im->cr);
3840 switch (im->imgformat) {
3843 cairo_status_t status;
3845 status = strlen(im->graphfile) ?
3846 cairo_surface_write_to_png(im->surface, im->graphfile)
3847 : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
3850 if (status != CAIRO_STATUS_SUCCESS) {
3851 rrd_set_error("Could not save png to '%s'", im->graphfile);
3857 if (strlen(im->graphfile)) {
3858 cairo_show_page(im->cr);
3860 cairo_surface_finish(im->surface);
3869 /*****************************************************
3871 *****************************************************/
3878 if ((im->gdes = (graph_desc_t *)
3879 rrd_realloc(im->gdes, (im->gdes_c)
3880 * sizeof(graph_desc_t))) == NULL) {
3881 rrd_set_error("realloc graph_descs");
3886 im->gdes[im->gdes_c - 1].step = im->step;
3887 im->gdes[im->gdes_c - 1].step_orig = im->step;
3888 im->gdes[im->gdes_c - 1].stack = 0;
3889 im->gdes[im->gdes_c - 1].linewidth = 0;
3890 im->gdes[im->gdes_c - 1].debug = 0;
3891 im->gdes[im->gdes_c - 1].start = im->start;
3892 im->gdes[im->gdes_c - 1].start_orig = im->start;
3893 im->gdes[im->gdes_c - 1].end = im->end;
3894 im->gdes[im->gdes_c - 1].end_orig = im->end;
3895 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3896 im->gdes[im->gdes_c - 1].data = NULL;
3897 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3898 im->gdes[im->gdes_c - 1].data_first = 0;
3899 im->gdes[im->gdes_c - 1].p_data = NULL;
3900 im->gdes[im->gdes_c - 1].rpnp = NULL;
3901 im->gdes[im->gdes_c - 1].p_dashes = NULL;
3902 im->gdes[im->gdes_c - 1].shift = 0.0;
3903 im->gdes[im->gdes_c - 1].dash = 0;
3904 im->gdes[im->gdes_c - 1].ndash = 0;
3905 im->gdes[im->gdes_c - 1].offset = 0;
3906 im->gdes[im->gdes_c - 1].col.red = 0.0;
3907 im->gdes[im->gdes_c - 1].col.green = 0.0;
3908 im->gdes[im->gdes_c - 1].col.blue = 0.0;
3909 im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3910 im->gdes[im->gdes_c - 1].col2.red = 0.0;
3911 im->gdes[im->gdes_c - 1].col2.green = 0.0;
3912 im->gdes[im->gdes_c - 1].col2.blue = 0.0;
3913 im->gdes[im->gdes_c - 1].col2.alpha = 0.0;
3914 im->gdes[im->gdes_c - 1].gradheight = 50.0;
3915 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3916 im->gdes[im->gdes_c - 1].format[0] = '\0';
3917 im->gdes[im->gdes_c - 1].strftm = 0;
3918 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3919 im->gdes[im->gdes_c - 1].ds = -1;
3920 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3921 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3922 im->gdes[im->gdes_c - 1].yrule = DNAN;
3923 im->gdes[im->gdes_c - 1].xrule = 0;
3924 im->gdes[im->gdes_c - 1].daemon[0] = 0;
3928 /* copies input untill the first unescaped colon is found
3929 or until input ends. backslashes have to be escaped as well */
3931 const char *const input,
3937 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3938 if (input[inp] == '\\'
3939 && input[inp + 1] != '\0'
3940 && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3941 output[outp++] = input[++inp];
3943 output[outp++] = input[inp];
3946 output[outp] = '\0';
3950 /* Now just a wrapper around rrd_graph_v */
3962 rrd_info_t *grinfo = NULL;
3965 grinfo = rrd_graph_v(argc, argv);
3971 if (strcmp(walker->key, "image_info") == 0) {
3974 (char**)rrd_realloc((*prdata),
3975 (prlines + 1) * sizeof(char *))) == NULL) {
3976 rrd_set_error("realloc prdata");
3979 /* imginfo goes to position 0 in the prdata array */
3980 (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3981 + 2) * sizeof(char));
3982 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3983 (*prdata)[prlines] = NULL;
3985 /* skip anything else */
3986 walker = walker->next;
3994 if (strcmp(walker->key, "image_width") == 0) {
3995 *xsize = walker->value.u_cnt;
3996 } else if (strcmp(walker->key, "image_height") == 0) {
3997 *ysize = walker->value.u_cnt;
3998 } else if (strcmp(walker->key, "value_min") == 0) {
3999 *ymin = walker->value.u_val;
4000 } else if (strcmp(walker->key, "value_max") == 0) {
4001 *ymax = walker->value.u_val;
4002 } else if (strncmp(walker->key, "print", 5) == 0) { /* keys are prdate[0..] */
4005 (char**)rrd_realloc((*prdata),
4006 (prlines + 1) * sizeof(char *))) == NULL) {
4007 rrd_set_error("realloc prdata");
4010 (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
4011 + 2) * sizeof(char));
4012 (*prdata)[prlines] = NULL;
4013 strcpy((*prdata)[prlines - 1], walker->value.u_str);
4014 } else if (strcmp(walker->key, "image") == 0) {
4015 if ( fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
4016 (stream ? stream : stdout)) == 0 && ferror(stream ? stream : stdout)){
4017 rrd_set_error("writing image");
4021 /* skip anything else */
4022 walker = walker->next;
4024 rrd_info_free(grinfo);
4029 /* Some surgery done on this function, it became ridiculously big.
4031 ** - initializing now in rrd_graph_init()
4032 ** - options parsing now in rrd_graph_options()
4033 ** - script parsing now in rrd_graph_script()
4036 /* have no better idea where to put it - rrd.h does not work */
4037 int rrd_graph_xport(image_desc_t *);
4039 rrd_info_t *rrd_graph_v(
4046 rrd_graph_init(&im);
4047 /* a dummy surface so that we can measure text sizes for placements */
4048 old_locale = setlocale(LC_NUMERIC, NULL);
4049 setlocale(LC_NUMERIC, "C");
4050 rrd_graph_options(argc, argv, &im);
4051 if (rrd_test_error()) {
4052 setlocale(LC_NUMERIC, old_locale); /* reenable locale */
4053 rrd_info_free(im.grinfo);
4058 if (optind >= argc) {
4059 setlocale(LC_NUMERIC, old_locale); /* reenable locale */
4060 rrd_info_free(im.grinfo);
4062 rrd_set_error("missing filename");
4066 if (strlen(argv[optind]) >= MAXPATH) {
4067 setlocale(LC_NUMERIC, old_locale); /* reenable locale */
4068 rrd_set_error("filename (including path) too long");
4069 rrd_info_free(im.grinfo);
4074 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
4075 im.graphfile[MAXPATH - 1] = '\0';
4077 if (strcmp(im.graphfile, "-") == 0) {
4078 im.graphfile[0] = '\0';
4081 rrd_graph_script(argc, argv, &im, 1);
4082 setlocale(LC_NUMERIC, old_locale); /* reenable locale for rendering the graph */
4084 if (rrd_test_error()) {
4085 rrd_info_free(im.grinfo);
4090 if (im.imgformat >= IF_XML) {
4091 rrd_graph_xport(&im);
4094 /* Everything is now read and the actual work can start */
4096 if (graph_paint(&im) == -1) {
4097 rrd_info_free(im.grinfo);
4103 /* The image is generated and needs to be output.
4104 ** Also, if needed, print a line with information about the image.
4112 path = strdup(im.graphfile);
4113 filename = basename(path);
4115 sprintf_alloc(im.imginfo,
4118 im.ximg), (long) (im.zoom * im.yimg));
4119 grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
4123 if (im.rendered_image) {
4126 img.u_blo.size = im.rendered_image_size;
4127 img.u_blo.ptr = im.rendered_image;
4128 grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
4137 image_desc_t *im,int prop,char *font, double size ){
4139 strncpy(im->text_prop[prop].font, font, sizeof(text_prop[prop].font) - 1);
4140 im->text_prop[prop].font[sizeof(text_prop[prop].font) - 1] = '\0';
4141 /* if we already got one, drop it first */
4142 pango_font_description_free(im->text_prop[prop].font_desc);
4143 im->text_prop[prop].font_desc = pango_font_description_from_string( font );
4146 im->text_prop[prop].size = size;
4148 if (im->text_prop[prop].font_desc && im->text_prop[prop].size ){
4149 pango_font_description_set_size(im->text_prop[prop].font_desc, im->text_prop[prop].size * PANGO_SCALE);
4153 void rrd_graph_init(
4158 char *deffont = getenv("RRD_DEFAULT_FONT");
4159 static PangoFontMap *fontmap = NULL;
4160 PangoContext *context;
4167 im->daemon_addr = NULL;
4168 im->draw_x_grid = 1;
4169 im->draw_y_grid = 1;
4170 im->draw_3d_border = 2;
4171 im->dynamic_labels = 0;
4172 im->extra_flags = 0;
4173 im->font_options = cairo_font_options_create();
4174 im->forceleftspace = 0;
4177 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4178 im->grid_dash_off = 1;
4179 im->grid_dash_on = 1;
4181 im->grinfo = (rrd_info_t *) NULL;
4182 im->grinfo_current = (rrd_info_t *) NULL;
4183 im->imgformat = IF_PNG;
4186 im->legenddirection = TOP_DOWN;
4187 im->legendheight = 0;
4188 im->legendposition = SOUTH;
4189 im->legendwidth = 0;
4190 im->logarithmic = 0;
4197 im->rendered_image_size = 0;
4198 im->rendered_image = NULL;
4202 im->tabwidth = 40.0;
4203 im->title[0] = '\0';
4204 im->unitsexponent = 9999;
4205 im->unitslength = 6;
4206 im->viewfactor = 1.0;
4207 im->watermark[0] = '\0';
4208 im->with_markup = 0;
4210 im->xlab_user.minsec = -1;
4212 im->xOriginLegend = 0;
4213 im->xOriginLegendY = 0;
4214 im->xOriginLegendY2 = 0;
4215 im->xOriginTitle = 0;
4217 im->ygridstep = DNAN;
4219 im->ylegend[0] = '\0';
4220 im->second_axis_scale = 0; /* 0 disables it */
4221 im->second_axis_shift = 0; /* no shift by default */
4222 im->second_axis_legend[0] = '\0';
4223 im->second_axis_format[0] = '\0';
4225 im->yOriginLegend = 0;
4226 im->yOriginLegendY = 0;
4227 im->yOriginLegendY2 = 0;
4228 im->yOriginTitle = 0;
4232 im->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
4233 im->cr = cairo_create(im->surface);
4235 for (i = 0; i < DIM(text_prop); i++) {
4236 im->text_prop[i].size = -1;
4237 im->text_prop[i].font_desc = NULL;
4238 rrd_set_font_desc(im,i, deffont ? deffont : text_prop[i].font,text_prop[i].size);
4241 if (fontmap == NULL){
4242 fontmap = pango_cairo_font_map_get_default();
4245 context = pango_cairo_font_map_create_context((PangoCairoFontMap*)fontmap);
4247 pango_cairo_context_set_resolution(context, 100);
4249 pango_cairo_update_context(im->cr,context);
4251 im->layout = pango_layout_new(context);
4252 g_object_unref (context);
4254 // im->layout = pango_cairo_create_layout(im->cr);
4257 cairo_font_options_set_hint_style
4258 (im->font_options, CAIRO_HINT_STYLE_FULL);
4259 cairo_font_options_set_hint_metrics
4260 (im->font_options, CAIRO_HINT_METRICS_ON);
4261 cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
4265 for (i = 0; i < DIM(graph_col); i++)
4266 im->graph_col[i] = graph_col[i];
4272 void rrd_graph_options(
4279 char *parsetime_error = NULL;
4280 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
4281 time_t start_tmp = 0, end_tmp = 0;
4283 rrd_time_value_t start_tv, end_tv;
4284 long unsigned int color;
4286 /* defines for long options without a short equivalent. should be bytes,
4287 and may not collide with (the ASCII value of) short options */
4288 #define LONGOPT_UNITS_SI 255
4291 struct option long_options[] = {
4292 { "alt-autoscale", no_argument, 0, 'A'},
4293 { "imgformat", required_argument, 0, 'a'},
4294 { "font-smoothing-threshold", required_argument, 0, 'B'},
4295 { "base", required_argument, 0, 'b'},
4296 { "color", required_argument, 0, 'c'},
4297 { "full-size-mode", no_argument, 0, 'D'},
4298 { "daemon", required_argument, 0, 'd'},
4299 { "slope-mode", no_argument, 0, 'E'},
4300 { "end", required_argument, 0, 'e'},
4301 { "force-rules-legend", no_argument, 0, 'F'},
4302 { "imginfo", required_argument, 0, 'f'},
4303 { "graph-render-mode", required_argument, 0, 'G'},
4304 { "no-legend", no_argument, 0, 'g'},
4305 { "height", required_argument, 0, 'h'},
4306 { "no-minor", no_argument, 0, 'I'},
4307 { "interlaced", no_argument, 0, 'i'},
4308 { "alt-autoscale-min", no_argument, 0, 'J'},
4309 { "only-graph", no_argument, 0, 'j'},
4310 { "units-length", required_argument, 0, 'L'},
4311 { "lower-limit", required_argument, 0, 'l'},
4312 { "alt-autoscale-max", no_argument, 0, 'M'},
4313 { "zoom", required_argument, 0, 'm'},
4314 { "no-gridfit", no_argument, 0, 'N'},
4315 { "font", required_argument, 0, 'n'},
4316 { "logarithmic", no_argument, 0, 'o'},
4317 { "pango-markup", no_argument, 0, 'P'},
4318 { "font-render-mode", required_argument, 0, 'R'},
4319 { "rigid", no_argument, 0, 'r'},
4320 { "step", required_argument, 0, 'S'},
4321 { "start", required_argument, 0, 's'},
4322 { "tabwidth", required_argument, 0, 'T'},
4323 { "title", required_argument, 0, 't'},
4324 { "upper-limit", required_argument, 0, 'u'},
4325 { "vertical-label", required_argument, 0, 'v'},
4326 { "watermark", required_argument, 0, 'W'},
4327 { "width", required_argument, 0, 'w'},
4328 { "units-exponent", required_argument, 0, 'X'},
4329 { "x-grid", required_argument, 0, 'x'},
4330 { "alt-y-grid", no_argument, 0, 'Y'},
4331 { "y-grid", required_argument, 0, 'y'},
4332 { "lazy", no_argument, 0, 'z'},
4333 { "units", required_argument, 0, LONGOPT_UNITS_SI},
4334 { "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 */
4335 { "disable-rrdtool-tag",no_argument, 0, 1001},
4336 { "right-axis", required_argument, 0, 1002},
4337 { "right-axis-label", required_argument, 0, 1003},
4338 { "right-axis-format", required_argument, 0, 1004},
4339 { "legend-position", required_argument, 0, 1005},
4340 { "legend-direction", required_argument, 0, 1006},
4341 { "border", required_argument, 0, 1007},
4342 { "grid-dash", required_argument, 0, 1008},
4343 { "dynamic-labels", no_argument, 0, 1009},
4344 { "week-fmt", required_argument, 0, 1010},
4350 opterr = 0; /* initialize getopt */
4351 rrd_parsetime("end-24h", &start_tv);
4352 rrd_parsetime("now", &end_tv);
4354 int option_index = 0;
4356 int col_start, col_end;
4358 opt = getopt_long(argc, argv,
4359 "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",
4360 long_options, &option_index);
4365 im->extra_flags |= NOMINOR;
4368 im->extra_flags |= ALTYGRID;
4371 im->extra_flags |= ALTAUTOSCALE;
4374 im->extra_flags |= ALTAUTOSCALE_MIN;
4377 im->extra_flags |= ALTAUTOSCALE_MAX;
4380 im->extra_flags |= ONLY_GRAPH;
4383 im->extra_flags |= NOLEGEND;
4386 if (strcmp(optarg, "north") == 0) {
4387 im->legendposition = NORTH;
4388 } else if (strcmp(optarg, "west") == 0) {
4389 im->legendposition = WEST;
4390 } else if (strcmp(optarg, "south") == 0) {
4391 im->legendposition = SOUTH;
4392 } else if (strcmp(optarg, "east") == 0) {
4393 im->legendposition = EAST;
4395 rrd_set_error("unknown legend-position '%s'", optarg);
4400 if (strcmp(optarg, "topdown") == 0) {
4401 im->legenddirection = TOP_DOWN;
4402 } else if (strcmp(optarg, "bottomup") == 0) {
4403 im->legenddirection = BOTTOM_UP;
4405 rrd_set_error("unknown legend-position '%s'", optarg);
4410 im->extra_flags |= FORCE_RULES_LEGEND;
4413 im->extra_flags |= NO_RRDTOOL_TAG;
4415 case LONGOPT_UNITS_SI:
4416 if (im->extra_flags & FORCE_UNITS) {
4417 rrd_set_error("--units can only be used once!");
4420 if (strcmp(optarg, "si") == 0)
4421 im->extra_flags |= FORCE_UNITS_SI;
4423 rrd_set_error("invalid argument for --units: %s", optarg);
4428 im->unitsexponent = atoi(optarg);
4431 im->unitslength = atoi(optarg);
4432 im->forceleftspace = 1;
4435 im->tabwidth = atof(optarg);
4438 im->step = atoi(optarg);
4444 im->with_markup = 1;
4447 if ((parsetime_error = rrd_parsetime(optarg, &start_tv))) {
4448 rrd_set_error("start time: %s", parsetime_error);
4453 if ((parsetime_error = rrd_parsetime(optarg, &end_tv))) {
4454 rrd_set_error("end time: %s", parsetime_error);
4459 if (strcmp(optarg, "none") == 0) {
4460 im->draw_x_grid = 0;
4464 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
4466 &im->xlab_user.gridst,
4468 &im->xlab_user.mgridst,
4470 &im->xlab_user.labst,
4471 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
4472 strncpy(im->xlab_form, optarg + stroff,
4473 sizeof(im->xlab_form) - 1);
4474 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
4476 (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
4477 rrd_set_error("unknown keyword %s", scan_gtm);
4480 (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
4482 rrd_set_error("unknown keyword %s", scan_mtm);
4485 (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
4486 rrd_set_error("unknown keyword %s", scan_ltm);
4489 im->xlab_user.minsec = 1;
4490 im->xlab_user.stst = im->xlab_form;
4492 rrd_set_error("invalid x-grid format");
4498 if (strcmp(optarg, "none") == 0) {
4499 im->draw_y_grid = 0;
4502 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
4503 if (im->ygridstep <= 0) {
4504 rrd_set_error("grid step must be > 0");
4506 } else if (im->ylabfact < 1) {
4507 rrd_set_error("label factor must be > 0");
4511 rrd_set_error("invalid y-grid format");
4516 im->draw_3d_border = atoi(optarg);
4518 case 1008: /* grid-dash */
4522 &im->grid_dash_off) != 2) {
4523 rrd_set_error("expected grid-dash format float:float");
4527 case 1009: /* enable dynamic labels */
4528 im->dynamic_labels = 1;
4531 strncpy(week_fmt,optarg,sizeof week_fmt);
4532 week_fmt[(sizeof week_fmt)-1]='\0';
4534 case 1002: /* right y axis */
4538 &im->second_axis_scale,
4539 &im->second_axis_shift) == 2) {
4540 if(im->second_axis_scale==0){
4541 rrd_set_error("the second_axis_scale must not be 0");
4545 rrd_set_error("invalid right-axis format expected scale:shift");
4550 strncpy(im->second_axis_legend,optarg,150);
4551 im->second_axis_legend[150]='\0';
4554 if (bad_format(optarg)){
4555 rrd_set_error("use either %le or %lf formats");
4558 strncpy(im->second_axis_format,optarg,150);
4559 im->second_axis_format[150]='\0';
4562 strncpy(im->ylegend, optarg, 150);
4563 im->ylegend[150] = '\0';
4566 im->maxval = atof(optarg);
4569 im->minval = atof(optarg);
4572 im->base = atol(optarg);
4573 if (im->base != 1024 && im->base != 1000) {
4575 ("the only sensible value for base apart from 1000 is 1024");
4580 long_tmp = atol(optarg);
4581 if (long_tmp < 10) {
4582 rrd_set_error("width below 10 pixels");
4585 im->xsize = long_tmp;
4588 long_tmp = atol(optarg);
4589 if (long_tmp < 10) {
4590 rrd_set_error("height below 10 pixels");
4593 im->ysize = long_tmp;
4596 im->extra_flags |= FULL_SIZE_MODE;
4599 /* interlaced png not supported at the moment */
4605 im->imginfo = optarg;
4609 (im->imgformat = if_conv(optarg)) == -1) {
4610 rrd_set_error("unsupported graphics format '%s'", optarg);
4621 im->logarithmic = 1;
4625 "%10[A-Z]#%n%8lx%n",
4626 col_nam, &col_start, &color, &col_end) == 2) {
4628 int col_len = col_end - col_start;
4633 (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4641 (((color & 0xF000) *
4642 0x11000) | ((color & 0x0F00) *
4643 0x01100) | ((color &
4646 ((color & 0x000F) * 0x00011)
4650 color = (color << 8) + 0xff /* shift left by 8 */ ;
4655 rrd_set_error("the color format is #RRGGBB[AA]");
4658 if ((ci = grc_conv(col_nam)) != -1) {
4659 im->graph_col[ci] = gfx_hex_to_col(color);
4661 rrd_set_error("invalid color name '%s'", col_nam);
4665 rrd_set_error("invalid color def format");
4674 if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4675 int sindex, propidx;
4677 if ((sindex = text_prop_conv(prop)) != -1) {
4678 for (propidx = sindex;
4679 propidx < TEXT_PROP_LAST; propidx++) {
4681 rrd_set_font_desc(im,propidx,NULL,size);
4683 if ((int) strlen(optarg) > end+2) {
4684 if (optarg[end] == ':') {
4685 rrd_set_font_desc(im,propidx,optarg + end + 1,0);
4688 ("expected : after font size in '%s'",
4693 /* only run the for loop for DEFAULT (0) for
4694 all others, we break here. woodo programming */
4695 if (propidx == sindex && sindex != 0)
4699 rrd_set_error("invalid fonttag '%s'", prop);
4703 rrd_set_error("invalid text property format");
4709 im->zoom = atof(optarg);
4710 if (im->zoom <= 0.0) {
4711 rrd_set_error("zoom factor must be > 0");
4716 strncpy(im->title, optarg, 150);
4717 im->title[150] = '\0';
4720 if (strcmp(optarg, "normal") == 0) {
4721 cairo_font_options_set_antialias
4722 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4723 cairo_font_options_set_hint_style
4724 (im->font_options, CAIRO_HINT_STYLE_FULL);
4725 } else if (strcmp(optarg, "light") == 0) {
4726 cairo_font_options_set_antialias
4727 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4728 cairo_font_options_set_hint_style
4729 (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4730 } else if (strcmp(optarg, "mono") == 0) {
4731 cairo_font_options_set_antialias
4732 (im->font_options, CAIRO_ANTIALIAS_NONE);
4733 cairo_font_options_set_hint_style
4734 (im->font_options, CAIRO_HINT_STYLE_FULL);
4736 rrd_set_error("unknown font-render-mode '%s'", optarg);
4741 if (strcmp(optarg, "normal") == 0)
4742 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4743 else if (strcmp(optarg, "mono") == 0)
4744 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4746 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4751 /* not supported curently */
4754 strncpy(im->watermark, optarg, 100);
4755 im->watermark[99] = '\0';
4759 if (im->daemon_addr != NULL)
4761 rrd_set_error ("You cannot specify --daemon "
4766 im->daemon_addr = strdup(optarg);
4767 if (im->daemon_addr == NULL)
4769 rrd_set_error("strdup failed");
4777 rrd_set_error("unknown option '%c'", optopt);
4779 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4784 pango_cairo_context_set_font_options(pango_layout_get_context(im->layout), im->font_options);
4785 pango_layout_context_changed(im->layout);
4789 if (im->logarithmic && im->minval <= 0) {
4791 ("for a logarithmic yaxis you must specify a lower-limit > 0");
4795 if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4796 /* error string is set in rrd_parsetime.c */
4800 if (start_tmp < 3600 * 24 * 365 * 10) {
4802 ("the first entry to fetch should be after 1980 (%ld)",
4807 if (end_tmp < start_tmp) {
4809 ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4813 im->start = start_tmp;
4815 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4818 int rrd_graph_color(
4826 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4828 color = strstr(var, "#");
4829 if (color == NULL) {
4830 if (optional == 0) {
4831 rrd_set_error("Found no color in %s", err);
4838 long unsigned int col;
4840 rest = strstr(color, ":");
4847 sscanf(color, "#%6lx%n", &col, &n);
4848 col = (col << 8) + 0xff /* shift left by 8 */ ;
4850 rrd_set_error("Color problem in %s", err);
4853 sscanf(color, "#%8lx%n", &col, &n);
4857 rrd_set_error("Color problem in %s", err);
4859 if (rrd_test_error())
4861 gdp->col = gfx_hex_to_col(col);
4874 while (*ptr != '\0')
4875 if (*ptr++ == '%') {
4877 /* line cannot end with percent char */
4880 /* '%s', '%S' and '%%' are allowed */
4881 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4883 /* %c is allowed (but use only with vdef!) */
4884 else if (*ptr == 'c') {
4889 /* or else '% 6.2lf' and such are allowed */
4891 /* optional padding character */
4892 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4894 /* This should take care of 'm.n' with all three optional */
4895 while (*ptr >= '0' && *ptr <= '9')
4899 while (*ptr >= '0' && *ptr <= '9')
4901 /* Either 'le', 'lf' or 'lg' must follow here */
4904 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4919 const char *const str)
4921 /* A VDEF currently is either "func" or "param,func"
4922 * so the parsing is rather simple. Change if needed.
4929 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4930 if (n == (int) strlen(str)) { /* matched */
4934 sscanf(str, "%29[A-Z]%n", func, &n);
4935 if (n == (int) strlen(str)) { /* matched */
4939 ("Unknown function string '%s' in VDEF '%s'",
4944 if (!strcmp("PERCENT", func))
4945 gdes->vf.op = VDEF_PERCENT;
4946 else if (!strcmp("PERCENTNAN", func))
4947 gdes->vf.op = VDEF_PERCENTNAN;
4948 else if (!strcmp("MAXIMUM", func))
4949 gdes->vf.op = VDEF_MAXIMUM;
4950 else if (!strcmp("AVERAGE", func))
4951 gdes->vf.op = VDEF_AVERAGE;
4952 else if (!strcmp("STDEV", func))
4953 gdes->vf.op = VDEF_STDEV;
4954 else if (!strcmp("MINIMUM", func))
4955 gdes->vf.op = VDEF_MINIMUM;
4956 else if (!strcmp("TOTAL", func))
4957 gdes->vf.op = VDEF_TOTAL;
4958 else if (!strcmp("FIRST", func))
4959 gdes->vf.op = VDEF_FIRST;
4960 else if (!strcmp("LAST", func))
4961 gdes->vf.op = VDEF_LAST;
4962 else if (!strcmp("LSLSLOPE", func))
4963 gdes->vf.op = VDEF_LSLSLOPE;
4964 else if (!strcmp("LSLINT", func))
4965 gdes->vf.op = VDEF_LSLINT;
4966 else if (!strcmp("LSLCORREL", func))
4967 gdes->vf.op = VDEF_LSLCORREL;
4970 ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
4973 switch (gdes->vf.op) {
4975 case VDEF_PERCENTNAN:
4976 if (isnan(param)) { /* no parameter given */
4978 ("Function '%s' needs parameter in VDEF '%s'\n",
4982 if (param >= 0.0 && param <= 100.0) {
4983 gdes->vf.param = param;
4984 gdes->vf.val = DNAN; /* undefined */
4985 gdes->vf.when = 0; /* undefined */
4989 ("Parameter '%f' out of range in VDEF '%s'\n",
4990 param, gdes->vname);
5003 case VDEF_LSLCORREL:
5005 gdes->vf.param = DNAN;
5006 gdes->vf.val = DNAN;
5011 ("Function '%s' needs no parameter in VDEF '%s'\n",
5025 graph_desc_t *src, *dst;
5029 dst = &im->gdes[gdi];
5030 src = &im->gdes[dst->vidx];
5031 data = src->data + src->ds;
5033 steps = (src->end - src->start) / src->step;
5036 ("DEBUG: start == %lu, end == %lu, %lu steps\n",
5037 src->start, src->end, steps);
5039 switch (dst->vf.op) {
5043 if ((array = (rrd_value_t*)malloc(steps * sizeof(double))) == NULL) {
5044 rrd_set_error("malloc VDEV_PERCENT");
5047 for (step = 0; step < steps; step++) {
5048 array[step] = data[step * src->ds_cnt];
5050 qsort(array, step, sizeof(double), vdef_percent_compar);
5051 field = round((dst->vf.param * (double)(steps - 1)) / 100.0);
5052 dst->vf.val = array[field];
5053 dst->vf.when = 0; /* no time component */
5057 for (step = 0; step < steps; step++)
5058 printf("DEBUG: %3li:%10.2f %c\n",
5059 step, array[step], step == field ? '*' : ' ');
5063 case VDEF_PERCENTNAN:{
5066 /* count number of "valid" values */
5068 for (step = 0; step < steps; step++) {
5069 if (!isnan(data[step * src->ds_cnt])) { nancount++; }
5071 /* and allocate it */
5072 if ((array = (rrd_value_t*)malloc(nancount * sizeof(double))) == NULL) {
5073 rrd_set_error("malloc VDEV_PERCENT");
5076 /* and fill it in */
5078 for (step = 0; step < steps; step++) {
5079 if (!isnan(data[step * src->ds_cnt])) {
5080 array[field] = data[step * src->ds_cnt];
5084 qsort(array, nancount, sizeof(double), vdef_percent_compar);
5085 field = round( dst->vf.param * (double)(nancount - 1) / 100.0);
5086 dst->vf.val = array[field];
5087 dst->vf.when = 0; /* no time component */
5094 while (step != steps && isnan(data[step * src->ds_cnt]))
5096 if (step == steps) {
5101 dst->vf.val = data[step * src->ds_cnt];
5102 dst->vf.when = src->start + (step + 1) * src->step;
5105 while (step != steps) {
5106 if (finite(data[step * src->ds_cnt])) {
5107 if (data[step * src->ds_cnt] > dst->vf.val) {
5108 dst->vf.val = data[step * src->ds_cnt];
5109 dst->vf.when = src->start + (step + 1) * src->step;
5121 double average = 0.0;
5123 for (step = 0; step < steps; step++) {
5124 if (finite(data[step * src->ds_cnt])) {
5125 sum += data[step * src->ds_cnt];
5130 if (dst->vf.op == VDEF_TOTAL) {
5131 dst->vf.val = sum * src->step;
5132 dst->vf.when = 0; /* no time component */
5134 } else if (dst->vf.op == VDEF_AVERAGE) {
5135 dst->vf.val = sum / cnt;
5136 dst->vf.when = 0; /* no time component */
5139 average = sum / cnt;
5141 for (step = 0; step < steps; step++) {
5142 if (finite(data[step * src->ds_cnt])) {
5143 sum += pow((data[step * src->ds_cnt] - average), 2.0);
5146 dst->vf.val = pow(sum / cnt, 0.5);
5147 dst->vf.when = 0; /* no time component */
5159 while (step != steps && isnan(data[step * src->ds_cnt]))
5161 if (step == steps) {
5166 dst->vf.val = data[step * src->ds_cnt];
5167 dst->vf.when = src->start + (step + 1) * src->step;
5170 while (step != steps) {
5171 if (finite(data[step * src->ds_cnt])) {
5172 if (data[step * src->ds_cnt] < dst->vf.val) {
5173 dst->vf.val = data[step * src->ds_cnt];
5174 dst->vf.when = src->start + (step + 1) * src->step;
5182 /* The time value returned here is one step before the
5183 * actual time value. This is the start of the first
5187 while (step != steps && isnan(data[step * src->ds_cnt]))
5189 if (step == steps) { /* all entries were NaN */
5194 dst->vf.val = data[step * src->ds_cnt];
5195 dst->vf.when = src->start + step * src->step;
5200 /* The time value returned here is the
5201 * actual time value. This is the end of the last
5205 while (step >= 0 && isnan(data[step * src->ds_cnt]))
5207 if (step < 0) { /* all entries were NaN */
5212 dst->vf.val = data[step * src->ds_cnt];
5213 dst->vf.when = src->start + (step + 1) * src->step;
5219 case VDEF_LSLCORREL:{
5220 /* Bestfit line by linear least squares method */
5223 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
5230 for (step = 0; step < steps; step++) {
5231 if (finite(data[step * src->ds_cnt])) {
5234 SUMxx += step * step;
5235 SUMxy += step * data[step * src->ds_cnt];
5236 SUMy += data[step * src->ds_cnt];
5237 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
5241 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
5242 y_intercept = (SUMy - slope * SUMx) / cnt;
5245 (SUMx * SUMy) / cnt) /
5247 (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
5249 if (dst->vf.op == VDEF_LSLSLOPE) {
5250 dst->vf.val = slope;
5253 } else if (dst->vf.op == VDEF_LSLINT) {
5254 dst->vf.val = y_intercept;
5257 } else if (dst->vf.op == VDEF_LSLCORREL) {
5258 dst->vf.val = correl;
5273 /* NaN < -INF < finite_values < INF */
5274 int vdef_percent_compar(
5280 /* Equality is not returned; this doesn't hurt except
5281 * (maybe) for a little performance.
5284 /* First catch NaN values. They are smallest */
5285 if (isnan(*(double *) a))
5287 if (isnan(*(double *) b))
5289 /* NaN doesn't reach this part so INF and -INF are extremes.
5290 * The sign from isinf() is compatible with the sign we return
5292 if (isinf(*(double *) a))
5293 return isinf(*(double *) a);
5294 if (isinf(*(double *) b))
5295 return isinf(*(double *) b);
5296 /* If we reach this, both values must be finite */
5297 if (*(double *) a < *(double *) b)
5306 rrd_info_type_t type,
5307 rrd_infoval_t value)
5309 im->grinfo_current = rrd_info_push(im->grinfo_current, key, type, value);
5310 if (im->grinfo == NULL) {
5311 im->grinfo = im->grinfo_current;
5322 /* Handling based on
5323 - ANSI C99 Specifications http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf
5324 - Single UNIX Specification version 2 http://www.opengroup.org/onlinepubs/007908799/xsh/strftime.html
5325 - POSIX:2001/Single UNIX Specification version 3 http://www.opengroup.org/onlinepubs/009695399/functions/strftime.html
5326 - POSIX:2008 Specifications http://www.opengroup.org/onlinepubs/9699919799/functions/strftime.html
5327 Specifications tells
5328 "If a conversion specifier is not one of the above, the behavior is undefined."
5331 "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.
5334 "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."
5336 POSIX:2008 introduce more complexe behavior that are not handled here.
5338 According to this, this code will replace:
5339 - % followed by @ by a %@
5340 - % followed by by a %SPACE
5341 - % followed by . by a %.
5342 - % followed by % by a %
5343 - % followed by t by a TAB
5344 - % followed by E then anything by '-'
5345 - % followed by O then anything by '-'
5346 - % followed by anything else by at least one '-'. More characters may be added to better fit expected output length
5350 for(j = 0; (j < FMT_LEG_LEN - 1) && (jj < FMT_LEG_LEN); j++) { /* we don't need to parse the last char */
5351 if (format[j] == '%') {
5352 if ((format[j+1] == 'E') || (format[j+1] == 'O')) {
5354 j+=2; /* We skip next 2 following char */
5355 } else if ((format[j+1] == 'C') || (format[j+1] == 'd') ||
5356 (format[j+1] == 'g') || (format[j+1] == 'H') ||
5357 (format[j+1] == 'I') || (format[j+1] == 'm') ||
5358 (format[j+1] == 'M') || (format[j+1] == 'S') ||
5359 (format[j+1] == 'U') || (format[j+1] == 'V') ||
5360 (format[j+1] == 'W') || (format[j+1] == 'y')) {
5362 if (jj < FMT_LEG_LEN) {
5365 j++; /* We skip the following char */
5366 } else if (format[j+1] == 'j') {
5368 if (jj < FMT_LEG_LEN - 1) {
5372 j++; /* We skip the following char */
5373 } else if ((format[j+1] == 'G') || (format[j+1] == 'Y')) {
5374 /* Assuming Year on 4 digit */
5376 if (jj < FMT_LEG_LEN - 2) {
5381 j++; /* We skip the following char */
5382 } else if (format[j+1] == 'R') {
5384 if (jj < FMT_LEG_LEN - 3) {
5390 j++; /* We skip the following char */
5391 } else if (format[j+1] == 'T') {
5393 if (jj < FMT_LEG_LEN - 6) {
5402 j++; /* We skip the following char */
5403 } else if (format[j+1] == 'F') {
5405 if (jj < FMT_LEG_LEN - 8) {
5416 j++; /* We skip the following char */
5417 } else if (format[j+1] == 'D') {
5419 if (jj < FMT_LEG_LEN - 6) {
5428 j++; /* We skip the following char */
5429 } else if (format[j+1] == 'n') {
5430 result[jj++] = '\r';
5431 result[jj++] = '\n';
5432 j++; /* We skip the following char */
5433 } else if (format[j+1] == 't') {
5434 result[jj++] = '\t';
5435 j++; /* We skip the following char */
5436 } else if (format[j+1] == '%') {
5438 j++; /* We skip the following char */
5439 } else if (format[j+1] == ' ') {
5440 if (jj < FMT_LEG_LEN - 1) {
5444 j++; /* We skip the following char */
5445 } else if (format[j+1] == '.') {
5446 if (jj < FMT_LEG_LEN - 1) {
5450 j++; /* We skip the following char */
5451 } else if (format[j+1] == '@') {
5452 if (jj < FMT_LEG_LEN - 1) {
5456 j++; /* We skip the following char */
5459 j++; /* We skip the following char */
5462 result[jj++] = format[j];
5465 result[jj] = '\0'; /* We must force the end of the string */