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(CSV, IF_CSV);
261 conv_if(TSV, IF_TSV);
262 conv_if(SSV, IF_SSV);
263 conv_if(JSON, IF_JSON);
265 return (enum gfx_if_en)(-1);
268 enum tmt_en tmt_conv(
272 conv_if(SECOND, TMT_SECOND);
273 conv_if(MINUTE, TMT_MINUTE);
274 conv_if(HOUR, TMT_HOUR);
275 conv_if(DAY, TMT_DAY);
276 conv_if(WEEK, TMT_WEEK);
277 conv_if(MONTH, TMT_MONTH);
278 conv_if(YEAR, TMT_YEAR);
279 return (enum tmt_en)(-1);
282 enum grc_en grc_conv(
286 conv_if(BACK, GRC_BACK);
287 conv_if(CANVAS, GRC_CANVAS);
288 conv_if(SHADEA, GRC_SHADEA);
289 conv_if(SHADEB, GRC_SHADEB);
290 conv_if(GRID, GRC_GRID);
291 conv_if(MGRID, GRC_MGRID);
292 conv_if(FONT, GRC_FONT);
293 conv_if(ARROW, GRC_ARROW);
294 conv_if(AXIS, GRC_AXIS);
295 conv_if(FRAME, GRC_FRAME);
297 return (enum grc_en)(-1);
300 enum text_prop_en text_prop_conv(
304 conv_if(DEFAULT, TEXT_PROP_DEFAULT);
305 conv_if(TITLE, TEXT_PROP_TITLE);
306 conv_if(AXIS, TEXT_PROP_AXIS);
307 conv_if(UNIT, TEXT_PROP_UNIT);
308 conv_if(LEGEND, TEXT_PROP_LEGEND);
309 conv_if(WATERMARK, TEXT_PROP_WATERMARK);
310 return (enum text_prop_en)(-1);
320 cairo_status_t status = (cairo_status_t) 0;
325 if (im->daemon_addr != NULL)
326 free(im->daemon_addr);
328 for (i = 0; i < (unsigned) im->gdes_c; i++) {
329 if (im->gdes[i].data_first) {
330 /* careful here, because a single pointer can occur several times */
331 free(im->gdes[i].data);
332 if (im->gdes[i].ds_namv) {
333 for (ii = 0; ii < im->gdes[i].ds_cnt; ii++)
334 free(im->gdes[i].ds_namv[ii]);
335 free(im->gdes[i].ds_namv);
338 /* free allocated memory used for dashed lines */
339 if (im->gdes[i].p_dashes != NULL)
340 free(im->gdes[i].p_dashes);
342 free(im->gdes[i].p_data);
343 free(im->gdes[i].rpnp);
347 for (i = 0; i < DIM(text_prop);i++){
348 pango_font_description_free(im->text_prop[i].font_desc);
349 im->text_prop[i].font_desc = NULL;
352 if (im->font_options)
353 cairo_font_options_destroy(im->font_options);
356 status = cairo_status(im->cr);
357 cairo_destroy(im->cr);
361 if (im->rendered_image) {
362 free(im->rendered_image);
366 g_object_unref (im->layout);
370 cairo_surface_destroy(im->surface);
373 fprintf(stderr, "OOPS: Cairo has issues it can't even die: %s\n",
374 cairo_status_to_string(status));
379 /* find SI magnitude symbol for the given number*/
381 image_desc_t *im, /* image description */
387 char *symbol[] = { "a", /* 10e-18 Atto */
388 "f", /* 10e-15 Femto */
389 "p", /* 10e-12 Pico */
390 "n", /* 10e-9 Nano */
391 "u", /* 10e-6 Micro */
392 "m", /* 10e-3 Milli */
397 "T", /* 10e12 Tera */
398 "P", /* 10e15 Peta */
405 if (*value == 0.0 || isnan(*value)) {
409 sindex = floor(log(fabs(*value)) / log((double) im->base));
410 *magfact = pow((double) im->base, (double) sindex);
411 (*value) /= (*magfact);
413 if (sindex <= symbcenter && sindex >= -symbcenter) {
414 (*symb_ptr) = symbol[sindex + symbcenter];
422 static char si_symbol[] = {
423 'y', /* 10e-24 Yocto */
424 'z', /* 10e-21 Zepto */
425 'a', /* 10e-18 Atto */
426 'f', /* 10e-15 Femto */
427 'p', /* 10e-12 Pico */
428 'n', /* 10e-9 Nano */
429 'u', /* 10e-6 Micro */
430 'm', /* 10e-3 Milli */
435 'T', /* 10e12 Tera */
436 'P', /* 10e15 Peta */
438 'Z', /* 10e21 Zeta */
439 'Y' /* 10e24 Yotta */
441 static const int si_symbcenter = 8;
443 /* find SI magnitude symbol for the numbers on the y-axis*/
445 image_desc_t *im /* image description */
449 double digits, viewdigits = 0;
452 floor(log(max(fabs(im->minval), fabs(im->maxval))) /
453 log((double) im->base));
455 if (im->unitsexponent != 9999) {
456 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
457 viewdigits = floor((double)(im->unitsexponent / 3));
462 im->magfact = pow((double) im->base, digits);
465 printf("digits %6.3f im->magfact %6.3f\n", digits, im->magfact);
468 im->viewfactor = im->magfact / pow((double) im->base, viewdigits);
470 if (((viewdigits + si_symbcenter) < sizeof(si_symbol)) &&
471 ((viewdigits + si_symbcenter) >= 0))
472 im->symbol = si_symbol[(int) viewdigits + si_symbcenter];
477 /* move min and max values around to become sensible */
482 double sensiblevalues[] = { 1000.0, 900.0, 800.0, 750.0, 700.0,
483 600.0, 500.0, 400.0, 300.0, 250.0,
484 200.0, 125.0, 100.0, 90.0, 80.0,
485 75.0, 70.0, 60.0, 50.0, 40.0, 30.0,
486 25.0, 20.0, 10.0, 9.0, 8.0,
487 7.0, 6.0, 5.0, 4.0, 3.5, 3.0,
488 2.5, 2.0, 1.8, 1.5, 1.2, 1.0,
489 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0, -1
492 double scaled_min, scaled_max;
499 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
500 im->minval, im->maxval, im->magfact);
503 if (isnan(im->ygridstep)) {
504 if (im->extra_flags & ALTAUTOSCALE) {
505 /* measure the amplitude of the function. Make sure that
506 graph boundaries are slightly higher then max/min vals
507 so we can see amplitude on the graph */
510 delt = im->maxval - im->minval;
512 fact = 2.0 * pow(10.0,
514 (max(fabs(im->minval), fabs(im->maxval)) /
517 adj = (fact - delt) * 0.55;
520 ("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n",
521 im->minval, im->maxval, delt, fact, adj);
526 } else if (im->extra_flags & ALTAUTOSCALE_MIN) {
527 /* measure the amplitude of the function. Make sure that
528 graph boundaries are slightly lower than min vals
529 so we can see amplitude on the graph */
530 adj = (im->maxval - im->minval) * 0.1;
532 } else if (im->extra_flags & ALTAUTOSCALE_MAX) {
533 /* measure the amplitude of the function. Make sure that
534 graph boundaries are slightly higher than max vals
535 so we can see amplitude on the graph */
536 adj = (im->maxval - im->minval) * 0.1;
539 scaled_min = im->minval / im->magfact;
540 scaled_max = im->maxval / im->magfact;
542 for (i = 1; sensiblevalues[i] > 0; i++) {
543 if (sensiblevalues[i - 1] >= scaled_min &&
544 sensiblevalues[i] <= scaled_min)
545 im->minval = sensiblevalues[i] * (im->magfact);
547 if (-sensiblevalues[i - 1] <= scaled_min &&
548 -sensiblevalues[i] >= scaled_min)
549 im->minval = -sensiblevalues[i - 1] * (im->magfact);
551 if (sensiblevalues[i - 1] >= scaled_max &&
552 sensiblevalues[i] <= scaled_max)
553 im->maxval = sensiblevalues[i - 1] * (im->magfact);
555 if (-sensiblevalues[i - 1] <= scaled_max &&
556 -sensiblevalues[i] >= scaled_max)
557 im->maxval = -sensiblevalues[i] * (im->magfact);
561 /* adjust min and max to the grid definition if there is one */
562 im->minval = (double) im->ylabfact * im->ygridstep *
563 floor(im->minval / ((double) im->ylabfact * im->ygridstep));
564 im->maxval = (double) im->ylabfact * im->ygridstep *
565 ceil(im->maxval / ((double) im->ylabfact * im->ygridstep));
569 fprintf(stderr, "SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
570 im->minval, im->maxval, im->magfact);
578 if (isnan(im->minval) || isnan(im->maxval))
581 if (im->logarithmic) {
582 double ya, yb, ypix, ypixfrac;
583 double log10_range = log10(im->maxval) - log10(im->minval);
585 ya = pow((double) 10, floor(log10(im->minval)));
586 while (ya < im->minval)
589 return; /* don't have y=10^x gridline */
591 if (yb <= im->maxval) {
592 /* we have at least 2 y=10^x gridlines.
593 Make sure distance between them in pixels
594 are an integer by expanding im->maxval */
595 double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
596 double factor = y_pixel_delta / floor(y_pixel_delta);
597 double new_log10_range = factor * log10_range;
598 double new_ymax_log10 = log10(im->minval) + new_log10_range;
600 im->maxval = pow(10, new_ymax_log10);
601 ytr(im, DNAN); /* reset precalc */
602 log10_range = log10(im->maxval) - log10(im->minval);
604 /* make sure first y=10^x gridline is located on
605 integer pixel position by moving scale slightly
606 downwards (sub-pixel movement) */
607 ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
608 ypixfrac = ypix - floor(ypix);
609 if (ypixfrac > 0 && ypixfrac < 1) {
610 double yfrac = ypixfrac / im->ysize;
612 im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
613 im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
614 ytr(im, DNAN); /* reset precalc */
617 /* Make sure we have an integer pixel distance between
618 each minor gridline */
619 double ypos1 = ytr(im, im->minval);
620 double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
621 double y_pixel_delta = ypos1 - ypos2;
622 double factor = y_pixel_delta / floor(y_pixel_delta);
623 double new_range = factor * (im->maxval - im->minval);
624 double gridstep = im->ygrid_scale.gridstep;
625 double minor_y, minor_y_px, minor_y_px_frac;
627 if (im->maxval > 0.0)
628 im->maxval = im->minval + new_range;
630 im->minval = im->maxval - new_range;
631 ytr(im, DNAN); /* reset precalc */
632 /* make sure first minor gridline is on integer pixel y coord */
633 minor_y = gridstep * floor(im->minval / gridstep);
634 while (minor_y < im->minval)
636 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
637 minor_y_px_frac = minor_y_px - floor(minor_y_px);
638 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
639 double yfrac = minor_y_px_frac / im->ysize;
640 double range = im->maxval - im->minval;
642 im->minval = im->minval - yfrac * range;
643 im->maxval = im->maxval - yfrac * range;
644 ytr(im, DNAN); /* reset precalc */
646 calc_horizontal_grid(im); /* recalc with changed im->maxval */
650 /* reduce data reimplementation by Alex */
653 enum cf_en cf, /* which consolidation function ? */
654 unsigned long cur_step, /* step the data currently is in */
655 time_t *start, /* start, end and step as requested ... */
656 time_t *end, /* ... by the application will be ... */
657 unsigned long *step, /* ... adjusted to represent reality */
658 unsigned long *ds_cnt, /* number of data sources in file */
660 { /* two dimensional array containing the data */
661 int i, reduce_factor = ceil((double) (*step) / (double) cur_step);
662 unsigned long col, dst_row, row_cnt, start_offset, end_offset, skiprows =
664 rrd_value_t *srcptr, *dstptr;
666 (*step) = cur_step * reduce_factor; /* set new step size for reduced data */
669 row_cnt = ((*end) - (*start)) / cur_step;
675 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
676 row_cnt, reduce_factor, *start, *end, cur_step);
677 for (col = 0; col < row_cnt; col++) {
678 printf("time %10lu: ", *start + (col + 1) * cur_step);
679 for (i = 0; i < *ds_cnt; i++)
680 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
685 /* We have to combine [reduce_factor] rows of the source
686 ** into one row for the destination. Doing this we also
687 ** need to take care to combine the correct rows. First
688 ** alter the start and end time so that they are multiples
689 ** of the new step time. We cannot reduce the amount of
690 ** time so we have to move the end towards the future and
691 ** the start towards the past.
693 end_offset = (*end) % (*step);
694 start_offset = (*start) % (*step);
696 /* If there is a start offset (which cannot be more than
697 ** one destination row), skip the appropriate number of
698 ** source rows and one destination row. The appropriate
699 ** number is what we do know (start_offset/cur_step) of
700 ** the new interval (*step/cur_step aka reduce_factor).
703 printf("start_offset: %lu end_offset: %lu\n", start_offset, end_offset);
704 printf("row_cnt before: %lu\n", row_cnt);
707 (*start) = (*start) - start_offset;
708 skiprows = reduce_factor - start_offset / cur_step;
709 srcptr += skiprows * *ds_cnt;
710 for (col = 0; col < (*ds_cnt); col++)
715 printf("row_cnt between: %lu\n", row_cnt);
718 /* At the end we have some rows that are not going to be
719 ** used, the amount is end_offset/cur_step
722 (*end) = (*end) - end_offset + (*step);
723 skiprows = end_offset / cur_step;
727 printf("row_cnt after: %lu\n", row_cnt);
730 /* Sanity check: row_cnt should be multiple of reduce_factor */
731 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
733 if (row_cnt % reduce_factor) {
734 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
735 row_cnt, reduce_factor);
736 printf("BUG in reduce_data()\n");
740 /* Now combine reduce_factor intervals at a time
741 ** into one interval for the destination.
744 for (dst_row = 0; (long int) row_cnt >= reduce_factor; dst_row++) {
745 for (col = 0; col < (*ds_cnt); col++) {
746 rrd_value_t newval = DNAN;
747 unsigned long validval = 0;
749 for (i = 0; i < reduce_factor; i++) {
750 if (isnan(srcptr[i * (*ds_cnt) + col])) {
755 newval = srcptr[i * (*ds_cnt) + col];
764 newval += srcptr[i * (*ds_cnt) + col];
767 newval = min(newval, srcptr[i * (*ds_cnt) + col]);
770 /* an interval contains a failure if any subintervals contained a failure */
772 newval = max(newval, srcptr[i * (*ds_cnt) + col]);
775 newval = srcptr[i * (*ds_cnt) + col];
801 srcptr += (*ds_cnt) * reduce_factor;
802 row_cnt -= reduce_factor;
804 /* If we had to alter the endtime, we didn't have enough
805 ** source rows to fill the last row. Fill it with NaN.
808 for (col = 0; col < (*ds_cnt); col++)
811 row_cnt = ((*end) - (*start)) / *step;
813 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
814 row_cnt, *start, *end, *step);
815 for (col = 0; col < row_cnt; col++) {
816 printf("time %10lu: ", *start + (col + 1) * (*step));
817 for (i = 0; i < *ds_cnt; i++)
818 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
825 /* get the data required for the graphs from the
834 /* pull the data from the rrd files ... */
835 for (i = 0; i < (int) im->gdes_c; i++) {
836 /* only GF_DEF elements fetch data */
837 if (im->gdes[i].gf != GF_DEF)
841 /* do we have it already ? */
842 for (ii = 0; ii < i; ii++) {
843 if (im->gdes[ii].gf != GF_DEF)
845 if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
846 && (im->gdes[i].cf == im->gdes[ii].cf)
847 && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
848 && (im->gdes[i].start_orig == im->gdes[ii].start_orig)
849 && (im->gdes[i].end_orig == im->gdes[ii].end_orig)
850 && (im->gdes[i].step_orig == im->gdes[ii].step_orig)) {
851 /* OK, the data is already there.
852 ** Just copy the header portion
854 im->gdes[i].start = im->gdes[ii].start;
855 im->gdes[i].end = im->gdes[ii].end;
856 im->gdes[i].step = im->gdes[ii].step;
857 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
858 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
859 im->gdes[i].data = im->gdes[ii].data;
860 im->gdes[i].data_first = 0;
867 unsigned long ft_step = im->gdes[i].step; /* ft_step will record what we got from fetch */
868 const char *rrd_daemon;
871 if (im->gdes[i].daemon[0] != 0)
872 rrd_daemon = im->gdes[i].daemon;
874 rrd_daemon = im->daemon_addr;
876 /* "daemon" may be NULL. ENV_RRDCACHED_ADDRESS is evaluated in that
877 * case. If "daemon" holds the same value as in the previous
878 * iteration, no actual new connection is established - the
879 * existing connection is re-used. */
880 rrdc_connect (rrd_daemon);
882 /* If connecting was successfull, use the daemon to query the data.
883 * If there is no connection, for example because no daemon address
884 * was specified, (try to) use the local file directly. */
885 if (rrdc_is_connected (rrd_daemon))
887 status = rrdc_fetch (im->gdes[i].rrd,
888 cf_to_string (im->gdes[i].cf),
893 &im->gdes[i].ds_namv,
900 if ((rrd_fetch_fn(im->gdes[i].rrd,
906 &im->gdes[i].ds_namv,
907 &im->gdes[i].data)) == -1) {
911 im->gdes[i].data_first = 1;
913 /* must reduce to at least im->step
914 otherwhise we end up with more data than we can handle in the
915 chart and visibility of data will be random */
916 im->gdes[i].step = max(im->gdes[i].step,im->step);
917 if (ft_step < im->gdes[i].step) {
918 reduce_data(im->gdes[i].cf_reduce,
923 &im->gdes[i].ds_cnt, &im->gdes[i].data);
925 im->gdes[i].step = ft_step;
929 /* lets see if the required data source is really there */
930 for (ii = 0; ii < (int) im->gdes[i].ds_cnt; ii++) {
931 if (strcmp(im->gdes[i].ds_namv[ii], im->gdes[i].ds_nam) == 0) {
935 if (im->gdes[i].ds == -1) {
936 rrd_set_error("No DS called '%s' in '%s'",
937 im->gdes[i].ds_nam, im->gdes[i].rrd);
945 /* evaluate the expressions in the CDEF functions */
947 /*************************************************************
949 *************************************************************/
951 long find_var_wrapper(
955 return find_var((image_desc_t *) arg1, key);
958 /* find gdes containing var*/
965 for (ii = 0; ii < im->gdes_c - 1; ii++) {
966 if ((im->gdes[ii].gf == GF_DEF
967 || im->gdes[ii].gf == GF_VDEF || im->gdes[ii].gf == GF_CDEF)
968 && (strcmp(im->gdes[ii].vname, key) == 0)) {
975 /* find the greatest common divisor for all the numbers
976 in the 0 terminated num array */
983 for (i = 0; num[i + 1] != 0; i++) {
985 rest = num[i] % num[i + 1];
991 /* return i==0?num[i]:num[i-1]; */
995 /* run the rpn calculator on all the VDEF and CDEF arguments */
1002 long *steparray, rpi;
1005 rpnstack_t rpnstack;
1007 rpnstack_init(&rpnstack);
1009 for (gdi = 0; gdi < im->gdes_c; gdi++) {
1010 /* Look for GF_VDEF and GF_CDEF in the same loop,
1011 * so CDEFs can use VDEFs and vice versa
1013 switch (im->gdes[gdi].gf) {
1017 graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
1019 /* remove current shift */
1020 vdp->start -= vdp->shift;
1021 vdp->end -= vdp->shift;
1024 if (im->gdes[gdi].shidx >= 0)
1025 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
1028 vdp->shift = im->gdes[gdi].shval;
1030 /* normalize shift to multiple of consolidated step */
1031 vdp->shift = (vdp->shift / (long) vdp->step) * (long) vdp->step;
1034 vdp->start += vdp->shift;
1035 vdp->end += vdp->shift;
1039 /* A VDEF has no DS. This also signals other parts
1040 * of rrdtool that this is a VDEF value, not a CDEF.
1042 im->gdes[gdi].ds_cnt = 0;
1043 if (vdef_calc(im, gdi)) {
1044 rrd_set_error("Error processing VDEF '%s'",
1045 im->gdes[gdi].vname);
1046 rpnstack_free(&rpnstack);
1051 im->gdes[gdi].ds_cnt = 1;
1052 im->gdes[gdi].ds = 0;
1053 im->gdes[gdi].data_first = 1;
1054 im->gdes[gdi].start = 0;
1055 im->gdes[gdi].end = 0;
1060 /* Find the variables in the expression.
1061 * - VDEF variables are substituted by their values
1062 * and the opcode is changed into OP_NUMBER.
1063 * - CDEF variables are analized for their step size,
1064 * the lowest common denominator of all the step
1065 * sizes of the data sources involved is calculated
1066 * and the resulting number is the step size for the
1067 * resulting data source.
1069 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1070 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1071 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1072 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1074 if (im->gdes[ptr].ds_cnt == 0) { /* this is a VDEF data source */
1077 ("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
1078 im->gdes[gdi].vname, im->gdes[ptr].vname);
1079 printf("DEBUG: value from vdef is %f\n",
1080 im->gdes[ptr].vf.val);
1082 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
1083 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
1084 } else { /* normal variables and PREF(variables) */
1086 /* add one entry to the array that keeps track of the step sizes of the
1087 * data sources going into the CDEF. */
1089 (long*)rrd_realloc(steparray,
1091 1) * sizeof(*steparray))) == NULL) {
1092 rrd_set_error("realloc steparray");
1093 rpnstack_free(&rpnstack);
1097 steparray[stepcnt - 1] = im->gdes[ptr].step;
1099 /* adjust start and end of cdef (gdi) so
1100 * that it runs from the latest start point
1101 * to the earliest endpoint of any of the
1102 * rras involved (ptr)
1105 if (im->gdes[gdi].start < im->gdes[ptr].start)
1106 im->gdes[gdi].start = im->gdes[ptr].start;
1108 if (im->gdes[gdi].end == 0 ||
1109 im->gdes[gdi].end > im->gdes[ptr].end)
1110 im->gdes[gdi].end = im->gdes[ptr].end;
1112 /* store pointer to the first element of
1113 * the rra providing data for variable,
1114 * further save step size and data source
1117 im->gdes[gdi].rpnp[rpi].data =
1118 im->gdes[ptr].data + im->gdes[ptr].ds;
1119 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
1120 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
1122 /* backoff the *.data ptr; this is done so
1123 * rpncalc() function doesn't have to treat
1124 * the first case differently
1126 } /* if ds_cnt != 0 */
1127 } /* if OP_VARIABLE */
1128 } /* loop through all rpi */
1130 /* move the data pointers to the correct period */
1131 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1132 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1133 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1134 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1136 im->gdes[gdi].start - im->gdes[ptr].start;
1139 im->gdes[gdi].rpnp[rpi].data +=
1140 (diff / im->gdes[ptr].step) *
1141 im->gdes[ptr].ds_cnt;
1145 if (steparray == NULL) {
1146 rrd_set_error("rpn expressions without DEF"
1147 " or CDEF variables are not supported");
1148 rpnstack_free(&rpnstack);
1151 steparray[stepcnt] = 0;
1152 /* Now find the resulting step. All steps in all
1153 * used RRAs have to be visited
1155 im->gdes[gdi].step = lcd(steparray);
1157 if ((im->gdes[gdi].data = (rrd_value_t*)malloc(((im->gdes[gdi].end -
1158 im->gdes[gdi].start)
1159 / im->gdes[gdi].step)
1160 * sizeof(double))) == NULL) {
1161 rrd_set_error("malloc im->gdes[gdi].data");
1162 rpnstack_free(&rpnstack);
1166 /* Step through the new cdef results array and
1167 * calculate the values
1169 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
1170 now <= im->gdes[gdi].end; now += im->gdes[gdi].step) {
1171 rpnp_t *rpnp = im->gdes[gdi].rpnp;
1173 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
1174 * in this case we are advancing by timesteps;
1175 * we use the fact that time_t is a synonym for long
1177 if (rpn_calc(rpnp, &rpnstack, (long) now,
1178 im->gdes[gdi].data, ++dataidx) == -1) {
1179 /* rpn_calc sets the error string */
1180 rpnstack_free(&rpnstack);
1183 } /* enumerate over time steps within a CDEF */
1188 } /* enumerate over CDEFs */
1189 rpnstack_free(&rpnstack);
1193 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
1194 /* yes we are loosing precision by doing tos with floats instead of doubles
1195 but it seems more stable this way. */
1197 static int AlmostEqual2sComplement(
1203 int aInt = *(int *) &A;
1204 int bInt = *(int *) &B;
1207 /* Make sure maxUlps is non-negative and small enough that the
1208 default NAN won't compare as equal to anything. */
1210 /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
1212 /* Make aInt lexicographically ordered as a twos-complement int */
1215 aInt = 0x80000000l - aInt;
1217 /* Make bInt lexicographically ordered as a twos-complement int */
1220 bInt = 0x80000000l - bInt;
1222 intDiff = abs(aInt - bInt);
1224 if (intDiff <= maxUlps)
1230 /* massage data so, that we get one value for each x coordinate in the graph */
1235 double pixstep = (double) (im->end - im->start)
1236 / (double) im->xsize; /* how much time
1237 passes in one pixel */
1239 double minval = DNAN, maxval = DNAN;
1241 unsigned long gr_time;
1243 /* memory for the processed data */
1244 for (i = 0; i < im->gdes_c; i++) {
1245 if ((im->gdes[i].gf == GF_LINE)
1246 || (im->gdes[i].gf == GF_AREA)
1247 || (im->gdes[i].gf == GF_TICK)
1248 || (im->gdes[i].gf == GF_GRAD)
1250 if ((im->gdes[i].p_data = (rrd_value_t*)malloc((im->xsize + 1)
1251 * sizeof(rrd_value_t))) == NULL) {
1252 rrd_set_error("malloc data_proc");
1258 for (i = 0; i < im->xsize; i++) { /* for each pixel */
1261 gr_time = im->start + pixstep * i; /* time of the current step */
1264 for (ii = 0; ii < im->gdes_c; ii++) {
1267 switch (im->gdes[ii].gf) {
1272 if (!im->gdes[ii].stack)
1274 value = im->gdes[ii].yrule;
1275 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1276 /* The time of the data doesn't necessarily match
1277 ** the time of the graph. Beware.
1279 vidx = im->gdes[ii].vidx;
1280 if (im->gdes[vidx].gf == GF_VDEF) {
1281 value = im->gdes[vidx].vf.val;
1283 if (((long int) gr_time >=
1284 (long int) im->gdes[vidx].start)
1285 && ((long int) gr_time <
1286 (long int) im->gdes[vidx].end)) {
1287 value = im->gdes[vidx].data[(unsigned long)
1293 im->gdes[vidx].step)
1294 * im->gdes[vidx].ds_cnt +
1301 if (!isnan(value)) {
1303 im->gdes[ii].p_data[i] = paintval;
1304 /* GF_TICK: the data values are not
1305 ** relevant for min and max
1307 if (finite(paintval) && im->gdes[ii].gf != GF_TICK) {
1308 if ((isnan(minval) || paintval < minval) &&
1309 !(im->logarithmic && paintval <= 0.0))
1311 if (isnan(maxval) || paintval > maxval)
1315 im->gdes[ii].p_data[i] = DNAN;
1320 ("STACK should already be turned into LINE or AREA here");
1329 /* if min or max have not been asigned a value this is because
1330 there was no data in the graph ... this is not good ...
1331 lets set these to dummy values then ... */
1333 if (im->logarithmic) {
1334 if (isnan(minval) || isnan(maxval) || maxval <= 0) {
1335 minval = 0.0; /* catching this right away below */
1338 /* in logarithm mode, where minval is smaller or equal
1339 to 0 make the beast just way smaller than maxval */
1341 minval = maxval / 10e8;
1344 if (isnan(minval) || isnan(maxval)) {
1350 /* adjust min and max values given by the user */
1351 /* for logscale we add something on top */
1352 if (isnan(im->minval)
1353 || ((!im->rigid) && im->minval > minval)
1355 if (im->logarithmic)
1356 im->minval = minval / 2.0;
1358 im->minval = minval;
1360 if (isnan(im->maxval)
1361 || (!im->rigid && im->maxval < maxval)
1363 if (im->logarithmic)
1364 im->maxval = maxval * 2.0;
1366 im->maxval = maxval;
1369 /* make sure min is smaller than max */
1370 if (im->minval > im->maxval) {
1372 im->minval = 0.99 * im->maxval;
1374 im->minval = 1.01 * im->maxval;
1377 /* make sure min and max are not equal */
1378 if (AlmostEqual2sComplement(im->minval, im->maxval, 4)) {
1384 /* make sure min and max are not both zero */
1385 if (AlmostEqual2sComplement(im->maxval, 0, 4)) {
1392 static int find_first_weekday(void){
1393 static int first_weekday = -1;
1394 if (first_weekday == -1){
1395 #ifdef HAVE__NL_TIME_WEEK_1STDAY
1396 /* according to http://sourceware.org/ml/libc-locales/2009-q1/msg00011.html */
1397 long week_1stday_l = (long) nl_langinfo (_NL_TIME_WEEK_1STDAY);
1398 if (week_1stday_l == 19971130) first_weekday = 0; /* Sun */
1399 else if (week_1stday_l == 19971201) first_weekday = 1; /* Mon */
1400 else first_weekday = 1; /* we go for a monday default */
1405 return first_weekday;
1408 /* identify the point where the first gridline, label ... gets placed */
1410 time_t find_first_time(
1411 time_t start, /* what is the initial time */
1412 enum tmt_en baseint, /* what is the basic interval */
1413 long basestep /* how many if these do we jump a time */
1418 localtime_r(&start, &tm);
1422 tm. tm_sec -= tm.tm_sec % basestep;
1427 tm. tm_min -= tm.tm_min % basestep;
1433 tm. tm_hour -= tm.tm_hour % basestep;
1437 /* we do NOT look at the basestep for this ... */
1444 /* we do NOT look at the basestep for this ... */
1448 tm. tm_mday -= tm.tm_wday - find_first_weekday();
1450 if (tm.tm_wday == 0 && find_first_weekday() > 0)
1451 tm. tm_mday -= 7; /* we want the *previous* week */
1459 tm. tm_mon -= tm.tm_mon % basestep;
1470 tm.tm_year + 1900) %basestep;
1476 /* identify the point where the next gridline, label ... gets placed */
1477 time_t find_next_time(
1478 time_t current, /* what is the initial time */
1479 enum tmt_en baseint, /* what is the basic interval */
1480 long basestep /* how many if these do we jump a time */
1486 localtime_r(¤t, &tm);
1490 case TMT_SECOND: limit = 7200; break;
1491 case TMT_MINUTE: limit = 120; break;
1492 case TMT_HOUR: limit = 2; break;
1493 default: limit = 2; break;
1498 tm. tm_sec += basestep;
1502 tm. tm_min += basestep;
1506 tm. tm_hour += basestep;
1510 tm. tm_mday += basestep;
1514 tm. tm_mday += 7 * basestep;
1518 tm. tm_mon += basestep;
1522 tm. tm_year += basestep;
1524 madetime = mktime(&tm);
1525 } while (madetime == -1 && limit-- >= 0); /* this is necessary to skip impossible times
1526 like the daylight saving time skips */
1532 /* calculate values required for PRINT and GPRINT functions */
1537 long i, ii, validsteps;
1540 int graphelement = 0;
1543 double magfact = -1;
1548 /* wow initializing tmvdef is quite a task :-) */
1549 time_t now = time(NULL);
1551 localtime_r(&now, &tmvdef);
1552 for (i = 0; i < im->gdes_c; i++) {
1553 vidx = im->gdes[i].vidx;
1554 switch (im->gdes[i].gf) {
1557 /* PRINT and GPRINT can now print VDEF generated values.
1558 * There's no need to do any calculations on them as these
1559 * calculations were already made.
1561 if (im->gdes[vidx].gf == GF_VDEF) { /* simply use vals */
1562 printval = im->gdes[vidx].vf.val;
1563 localtime_r(&im->gdes[vidx].vf.when, &tmvdef);
1564 } else { /* need to calculate max,min,avg etcetera */
1565 max_ii = ((im->gdes[vidx].end - im->gdes[vidx].start)
1566 / im->gdes[vidx].step * im->gdes[vidx].ds_cnt);
1569 for (ii = im->gdes[vidx].ds;
1570 ii < max_ii; ii += im->gdes[vidx].ds_cnt) {
1571 if (!finite(im->gdes[vidx].data[ii]))
1573 if (isnan(printval)) {
1574 printval = im->gdes[vidx].data[ii];
1579 switch (im->gdes[i].cf) {
1583 case CF_DEVSEASONAL:
1587 printval += im->gdes[vidx].data[ii];
1590 printval = min(printval, im->gdes[vidx].data[ii]);
1594 printval = max(printval, im->gdes[vidx].data[ii]);
1597 printval = im->gdes[vidx].data[ii];
1600 if (im->gdes[i].cf == CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1601 if (validsteps > 1) {
1602 printval = (printval / validsteps);
1605 } /* prepare printval */
1607 if (!im->gdes[i].strftm && (percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
1608 /* Magfact is set to -1 upon entry to print_calc. If it
1609 * is still less than 0, then we need to run auto_scale.
1610 * Otherwise, put the value into the correct units. If
1611 * the value is 0, then do not set the symbol or magnification
1612 * so next the calculation will be performed again. */
1613 if (magfact < 0.0) {
1614 auto_scale(im, &printval, &si_symb, &magfact);
1615 if (printval == 0.0)
1618 printval /= magfact;
1620 *(++percent_s) = 's';
1621 } else if (!im->gdes[i].strftm && strstr(im->gdes[i].format, "%s") != NULL) {
1622 auto_scale(im, &printval, &si_symb, &magfact);
1625 if (im->gdes[i].gf == GF_PRINT) {
1626 rrd_infoval_t prline;
1628 if (im->gdes[i].strftm) {
1629 prline.u_str = (char*)malloc((FMT_LEG_LEN + 2) * sizeof(char));
1630 if (im->gdes[vidx].vf.never == 1) {
1631 time_clean(prline.u_str, im->gdes[i].format);
1633 strftime(prline.u_str,
1634 FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1636 } else if (bad_format(im->gdes[i].format)) {
1638 ("bad format for PRINT in '%s'", im->gdes[i].format);
1642 sprintf_alloc(im->gdes[i].format, printval, si_symb);
1646 ("print[%ld]", prline_cnt++), RD_I_STR, prline);
1651 if (im->gdes[i].strftm) {
1652 if (im->gdes[vidx].vf.never == 1) {
1653 time_clean(im->gdes[i].legend, im->gdes[i].format);
1655 strftime(im->gdes[i].legend,
1656 FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1659 if (bad_format(im->gdes[i].format)) {
1661 ("bad format for GPRINT in '%s'",
1662 im->gdes[i].format);
1665 #ifdef HAVE_SNPRINTF
1666 snprintf(im->gdes[i].legend,
1668 im->gdes[i].format, printval, si_symb);
1670 sprintf(im->gdes[i].legend,
1671 im->gdes[i].format, printval, si_symb);
1684 if (isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1685 im->gdes[i].yrule = im->gdes[vidx].vf.val;
1690 if (im->gdes[i].xrule == 0) { /* again ... the legend printer needs it */
1691 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1700 #ifdef WITH_PIECHART
1708 ("STACK should already be turned into LINE or AREA here");
1713 return graphelement;
1718 /* place legends with color spots */
1724 int interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1725 int border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1726 int fill = 0, fill_last;
1727 double legendwidth; // = im->ximg - 2 * border;
1729 double leg_x = border;
1730 int leg_y = 0; //im->yimg;
1731 int leg_y_prev = 0; // im->yimg;
1734 int i, ii, mark = 0;
1735 char default_txtalign = TXA_JUSTIFIED; /*default line orientation */
1738 char saved_legend[FMT_LEG_LEN + 5];
1744 legendwidth = im->legendwidth - 2 * border;
1748 if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
1749 if ((legspace = (int*)malloc(im->gdes_c * sizeof(int))) == NULL) {
1750 rrd_set_error("malloc for legspace");
1754 for (i = 0; i < im->gdes_c; i++) {
1755 char prt_fctn; /*special printfunctions */
1757 strcpy(saved_legend, im->gdes[i].legend);
1761 /* hide legends for rules which are not displayed */
1762 if (im->gdes[i].gf == GF_TEXTALIGN) {
1763 default_txtalign = im->gdes[i].txtalign;
1766 if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1767 if (im->gdes[i].gf == GF_HRULE
1768 && (im->gdes[i].yrule <
1769 im->minval || im->gdes[i].yrule > im->maxval))
1770 im->gdes[i].legend[0] = '\0';
1771 if (im->gdes[i].gf == GF_VRULE
1772 && (im->gdes[i].xrule <
1773 im->start || im->gdes[i].xrule > im->end))
1774 im->gdes[i].legend[0] = '\0';
1777 /* turn \\t into tab */
1778 while ((tab = strstr(im->gdes[i].legend, "\\t"))) {
1779 memmove(tab, tab + 1, strlen(tab));
1783 leg_cc = strlen(im->gdes[i].legend);
1784 /* is there a controle code at the end of the legend string ? */
1785 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\') {
1786 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1788 im->gdes[i].legend[leg_cc] = '\0';
1792 /* only valid control codes */
1793 if (prt_fctn != 'l' && prt_fctn != 'n' && /* a synonym for l */
1799 prt_fctn != 's' && prt_fctn != '\0' && prt_fctn != 'g') {
1802 ("Unknown control code at the end of '%s\\%c'",
1803 im->gdes[i].legend, prt_fctn);
1807 if (prt_fctn == 'n') {
1810 /* \. is a null operation to allow strings ending in \x */
1811 if (prt_fctn == '.') {
1815 /* remove exess space from the end of the legend for \g */
1816 while (prt_fctn == 'g' &&
1817 leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1819 im->gdes[i].legend[leg_cc] = '\0';
1824 /* no interleg space if string ends in \g */
1825 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1827 fill += legspace[i];
1830 gfx_get_text_width(im,
1836 im->tabwidth, im->gdes[i].legend);
1841 /* who said there was a special tag ... ? */
1842 if (prt_fctn == 'g') {
1846 if (prt_fctn == '\0') {
1847 if(calc_width && (fill > legendwidth)){
1850 if (i == im->gdes_c - 1 || fill > legendwidth) {
1851 /* just one legend item is left right or center */
1852 switch (default_txtalign) {
1867 /* is it time to place the legends ? */
1868 if (fill > legendwidth) {
1876 if (leg_c == 1 && prt_fctn == 'j') {
1881 if (prt_fctn != '\0') {
1883 if (leg_c >= 2 && prt_fctn == 'j') {
1884 glue = (double)(legendwidth - fill) / (double)(leg_c - 1);
1888 if (prt_fctn == 'c')
1889 leg_x = border + (double)(legendwidth - fill) / 2.0;
1890 if (prt_fctn == 'r')
1891 leg_x = legendwidth - fill + border;
1892 for (ii = mark; ii <= i; ii++) {
1893 if (im->gdes[ii].legend[0] == '\0')
1894 continue; /* skip empty legends */
1895 im->gdes[ii].leg_x = leg_x;
1896 im->gdes[ii].leg_y = leg_y + border;
1898 (double)gfx_get_text_width(im, leg_x,
1903 im->tabwidth, im->gdes[ii].legend)
1904 +(double)legspace[ii]
1908 if (leg_x > border || prt_fctn == 's')
1909 leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1910 if (prt_fctn == 's')
1911 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1912 if (prt_fctn == 'u')
1913 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size *1.8;
1915 if(calc_width && (fill > legendwidth)){
1924 strcpy(im->gdes[i].legend, saved_legend);
1929 im->legendwidth = legendwidth + 2 * border;
1932 im->legendheight = leg_y + border * 0.6;
1939 /* create a grid on the graph. it determines what to do
1940 from the values of xsize, start and end */
1942 /* the xaxis labels are determined from the number of seconds per pixel
1943 in the requested graph */
1945 int calc_horizontal_grid(
1953 int decimals, fractionals;
1955 im->ygrid_scale.labfact = 2;
1956 range = im->maxval - im->minval;
1957 scaledrange = range / im->magfact;
1958 /* does the scale of this graph make it impossible to put lines
1959 on it? If so, give up. */
1960 if (isnan(scaledrange)) {
1964 /* find grid spaceing */
1966 if (isnan(im->ygridstep)) {
1967 if (im->extra_flags & ALTYGRID) {
1968 /* find the value with max number of digits. Get number of digits */
1971 (max(fabs(im->maxval), fabs(im->minval)) *
1972 im->viewfactor / im->magfact));
1973 if (decimals <= 0) /* everything is small. make place for zero */
1975 im->ygrid_scale.gridstep =
1977 floor(log10(range * im->viewfactor / im->magfact))) /
1978 im->viewfactor * im->magfact;
1979 if (im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1980 im->ygrid_scale.gridstep = 0.1;
1981 /* should have at least 5 lines but no more then 15 */
1982 if (range / im->ygrid_scale.gridstep < 5
1983 && im->ygrid_scale.gridstep >= 30)
1984 im->ygrid_scale.gridstep /= 10;
1985 if (range / im->ygrid_scale.gridstep > 15)
1986 im->ygrid_scale.gridstep *= 10;
1987 if (range / im->ygrid_scale.gridstep > 5) {
1988 im->ygrid_scale.labfact = 1;
1989 if (range / im->ygrid_scale.gridstep > 8
1990 || im->ygrid_scale.gridstep <
1991 1.8 * im->text_prop[TEXT_PROP_AXIS].size)
1992 im->ygrid_scale.labfact = 2;
1994 im->ygrid_scale.gridstep /= 5;
1995 im->ygrid_scale.labfact = 5;
1999 (im->ygrid_scale.gridstep *
2000 (double) im->ygrid_scale.labfact * im->viewfactor /
2002 if (fractionals < 0) { /* small amplitude. */
2003 int len = decimals - fractionals + 1;
2005 if (im->unitslength < len + 2)
2006 im->unitslength = len + 2;
2007 sprintf(im->ygrid_scale.labfmt,
2009 -fractionals, (im->symbol != ' ' ? " %c" : ""));
2011 int len = decimals + 1;
2013 if (im->unitslength < len + 2)
2014 im->unitslength = len + 2;
2015 sprintf(im->ygrid_scale.labfmt,
2016 "%%%d.0f%s", len, (im->symbol != ' ' ? " %c" : ""));
2018 } else { /* classic rrd grid */
2019 for (i = 0; ylab[i].grid > 0; i++) {
2020 pixel = im->ysize / (scaledrange / ylab[i].grid);
2026 for (i = 0; i < 4; i++) {
2027 if (pixel * ylab[gridind].lfac[i] >=
2028 1.8 * im->text_prop[TEXT_PROP_AXIS].size) {
2029 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
2034 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
2037 im->ygrid_scale.gridstep = im->ygridstep;
2038 im->ygrid_scale.labfact = im->ylabfact;
2043 int draw_horizontal_grid(
2049 char graph_label[100];
2051 double X0 = im->xorigin;
2052 double X1 = im->xorigin + im->xsize;
2053 int sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
2054 int egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
2056 double second_axis_magfact = 0;
2057 char *second_axis_symb = "";
2060 im->ygrid_scale.gridstep /
2061 (double) im->magfact * (double) im->viewfactor;
2062 MaxY = scaledstep * (double) egrid;
2063 for (i = sgrid; i <= egrid; i++) {
2065 im->ygrid_scale.gridstep * i);
2067 im->ygrid_scale.gridstep * (i + 1));
2069 if (floor(Y0 + 0.5) >=
2070 im->yorigin - im->ysize && floor(Y0 + 0.5) <= im->yorigin) {
2071 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
2072 with the chosen settings. Add a label if required by settings, or if
2073 there is only one label so far and the next grid line is out of bounds. */
2074 if (i % im->ygrid_scale.labfact == 0
2076 && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
2077 if (im->symbol == ' ') {
2078 if (im->extra_flags & ALTYGRID) {
2079 sprintf(graph_label,
2080 im->ygrid_scale.labfmt,
2081 scaledstep * (double) i);
2084 sprintf(graph_label, "%4.1f",
2085 scaledstep * (double) i);
2087 sprintf(graph_label, "%4.0f",
2088 scaledstep * (double) i);
2092 char sisym = (i == 0 ? ' ' : im->symbol);
2094 if (im->extra_flags & ALTYGRID) {
2095 sprintf(graph_label,
2096 im->ygrid_scale.labfmt,
2097 scaledstep * (double) i, sisym);
2100 sprintf(graph_label, "%4.1f %c",
2101 scaledstep * (double) i, sisym);
2103 sprintf(graph_label, "%4.0f %c",
2104 scaledstep * (double) i, sisym);
2109 if (im->second_axis_scale != 0){
2110 char graph_label_right[100];
2111 double sval = im->ygrid_scale.gridstep*(double)i*im->second_axis_scale+im->second_axis_shift;
2112 if (im->second_axis_format[0] == '\0'){
2113 if (!second_axis_magfact){
2114 double dummy = im->ygrid_scale.gridstep*(double)(sgrid+egrid)/2.0*im->second_axis_scale+im->second_axis_shift;
2115 auto_scale(im,&dummy,&second_axis_symb,&second_axis_magfact);
2117 sval /= second_axis_magfact;
2120 sprintf(graph_label_right,"%5.1f %s",sval,second_axis_symb);
2122 sprintf(graph_label_right,"%5.0f %s",sval,second_axis_symb);
2126 sprintf(graph_label_right,im->second_axis_format,sval);
2130 im->graph_col[GRC_FONT],
2131 im->text_prop[TEXT_PROP_AXIS].font_desc,
2132 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2133 graph_label_right );
2139 text_prop[TEXT_PROP_AXIS].
2141 im->graph_col[GRC_FONT],
2143 text_prop[TEXT_PROP_AXIS].
2146 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2147 gfx_line(im, X0 - 2, Y0, X0, Y0,
2148 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2149 gfx_line(im, X1, Y0, X1 + 2, Y0,
2150 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2151 gfx_dashed_line(im, X0 - 2, Y0,
2157 im->grid_dash_on, im->grid_dash_off);
2158 } else if (!(im->extra_flags & NOMINOR)) {
2161 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2162 gfx_line(im, X1, Y0, X1 + 2, Y0,
2163 GRIDWIDTH, im->graph_col[GRC_GRID]);
2164 gfx_dashed_line(im, X0 - 1, Y0,
2168 graph_col[GRC_GRID],
2169 im->grid_dash_on, im->grid_dash_off);
2176 /* this is frexp for base 10 */
2187 iexp = floor(log((double)fabs(x)) / log((double)10));
2188 mnt = x / pow(10.0, iexp);
2191 mnt = x / pow(10.0, iexp);
2198 /* logaritmic horizontal grid */
2199 int horizontal_log_grid(
2203 double yloglab[][10] = {
2205 1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0,
2207 1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0,
2209 1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0,
2226 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} /* last line */
2228 int i, j, val_exp, min_exp;
2229 double nex; /* number of decades in data */
2230 double logscale; /* scale in logarithmic space */
2231 int exfrac = 1; /* decade spacing */
2232 int mid = -1; /* row in yloglab for major grid */
2233 double mspac; /* smallest major grid spacing (pixels) */
2234 int flab; /* first value in yloglab to use */
2235 double value, tmp, pre_value;
2237 char graph_label[100];
2239 nex = log10(im->maxval / im->minval);
2240 logscale = im->ysize / nex;
2241 /* major spacing for data with high dynamic range */
2242 while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2249 /* major spacing for less dynamic data */
2251 /* search best row in yloglab */
2253 for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2254 mspac = logscale * log10(10.0 / yloglab[mid][i]);
2257 2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
2260 /* find first value in yloglab */
2262 yloglab[mid][flab] < 10
2263 && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2264 if (yloglab[mid][flab] == 10.0) {
2269 if (val_exp % exfrac)
2270 val_exp += abs(-val_exp % exfrac);
2272 X1 = im->xorigin + im->xsize;
2277 value = yloglab[mid][flab] * pow(10.0, val_exp);
2278 if (AlmostEqual2sComplement(value, pre_value, 4))
2279 break; /* it seems we are not converging */
2281 Y0 = ytr(im, value);
2282 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2284 /* major grid line */
2286 X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2287 gfx_line(im, X1, Y0, X1 + 2, Y0,
2288 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2289 gfx_dashed_line(im, X0 - 2, Y0,
2294 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2296 if (im->extra_flags & FORCE_UNITS_SI) {
2301 scale = floor(val_exp / 3.0);
2303 pvalue = pow(10.0, val_exp % 3);
2305 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2306 pvalue *= yloglab[mid][flab];
2307 if (((scale + si_symbcenter) < (int) sizeof(si_symbol))
2308 && ((scale + si_symbcenter) >= 0))
2309 symbol = si_symbol[scale + si_symbcenter];
2312 sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2314 sprintf(graph_label, "%3.0e", value);
2316 if (im->second_axis_scale != 0){
2317 char graph_label_right[100];
2318 double sval = value*im->second_axis_scale+im->second_axis_shift;
2319 if (im->second_axis_format[0] == '\0'){
2320 if (im->extra_flags & FORCE_UNITS_SI) {
2323 auto_scale(im,&sval,&symb,&mfac);
2324 sprintf(graph_label_right,"%4.0f %s", sval,symb);
2327 sprintf(graph_label_right,"%3.0e", sval);
2331 sprintf(graph_label_right,im->second_axis_format,sval,"");
2336 im->graph_col[GRC_FONT],
2337 im->text_prop[TEXT_PROP_AXIS].font_desc,
2338 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2339 graph_label_right );
2345 text_prop[TEXT_PROP_AXIS].
2347 im->graph_col[GRC_FONT],
2349 text_prop[TEXT_PROP_AXIS].
2352 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2354 if (mid < 4 && exfrac == 1) {
2355 /* find first and last minor line behind current major line
2356 * i is the first line and j tha last */
2358 min_exp = val_exp - 1;
2359 for (i = 1; yloglab[mid][i] < 10.0; i++);
2360 i = yloglab[mid][i - 1] + 1;
2364 i = yloglab[mid][flab - 1] + 1;
2365 j = yloglab[mid][flab];
2368 /* draw minor lines below current major line */
2369 for (; i < j; i++) {
2371 value = i * pow(10.0, min_exp);
2372 if (value < im->minval)
2374 Y0 = ytr(im, value);
2375 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2380 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2381 gfx_line(im, X1, Y0, X1 + 2, Y0,
2382 GRIDWIDTH, im->graph_col[GRC_GRID]);
2383 gfx_dashed_line(im, X0 - 1, Y0,
2387 graph_col[GRC_GRID],
2388 im->grid_dash_on, im->grid_dash_off);
2390 } else if (exfrac > 1) {
2391 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2392 value = pow(10.0, i);
2393 if (value < im->minval)
2395 Y0 = ytr(im, value);
2396 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2401 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2402 gfx_line(im, X1, Y0, X1 + 2, Y0,
2403 GRIDWIDTH, im->graph_col[GRC_GRID]);
2404 gfx_dashed_line(im, X0 - 1, Y0,
2408 graph_col[GRC_GRID],
2409 im->grid_dash_on, im->grid_dash_off);
2414 if (yloglab[mid][++flab] == 10.0) {
2420 /* draw minor lines after highest major line */
2421 if (mid < 4 && exfrac == 1) {
2422 /* find first and last minor line below current major line
2423 * i is the first line and j tha last */
2425 min_exp = val_exp - 1;
2426 for (i = 1; yloglab[mid][i] < 10.0; i++);
2427 i = yloglab[mid][i - 1] + 1;
2431 i = yloglab[mid][flab - 1] + 1;
2432 j = yloglab[mid][flab];
2435 /* draw minor lines below current major line */
2436 for (; i < j; i++) {
2438 value = i * pow(10.0, min_exp);
2439 if (value < im->minval)
2441 Y0 = ytr(im, value);
2442 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2446 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2447 gfx_line(im, X1, Y0, X1 + 2, Y0,
2448 GRIDWIDTH, im->graph_col[GRC_GRID]);
2449 gfx_dashed_line(im, X0 - 1, Y0,
2453 graph_col[GRC_GRID],
2454 im->grid_dash_on, im->grid_dash_off);
2457 /* fancy minor gridlines */
2458 else if (exfrac > 1) {
2459 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2460 value = pow(10.0, i);
2461 if (value < im->minval)
2463 Y0 = ytr(im, value);
2464 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2468 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2469 gfx_line(im, X1, Y0, X1 + 2, Y0,
2470 GRIDWIDTH, im->graph_col[GRC_GRID]);
2471 gfx_dashed_line(im, X0 - 1, Y0,
2475 graph_col[GRC_GRID],
2476 im->grid_dash_on, im->grid_dash_off);
2487 int xlab_sel; /* which sort of label and grid ? */
2488 time_t ti, tilab, timajor;
2490 char graph_label[100];
2491 double X0, Y0, Y1; /* points for filled graph and more */
2494 /* the type of time grid is determined by finding
2495 the number of seconds per pixel in the graph */
2496 if (im->xlab_user.minsec == -1) {
2497 factor = (im->end - im->start) / im->xsize;
2499 while (xlab[xlab_sel + 1].minsec !=
2500 -1 && xlab[xlab_sel + 1].minsec <= factor) {
2502 } /* pick the last one */
2503 while (xlab[xlab_sel - 1].minsec ==
2504 xlab[xlab_sel].minsec
2505 && xlab[xlab_sel].length > (im->end - im->start)) {
2507 } /* go back to the smallest size */
2508 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2509 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2510 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2511 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2512 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2513 im->xlab_user.labst = xlab[xlab_sel].labst;
2514 im->xlab_user.precis = xlab[xlab_sel].precis;
2515 im->xlab_user.stst = xlab[xlab_sel].stst;
2518 /* y coords are the same for every line ... */
2520 Y1 = im->yorigin - im->ysize;
2521 /* paint the minor grid */
2522 if (!(im->extra_flags & NOMINOR)) {
2523 for (ti = find_first_time(im->start,
2531 find_first_time(im->start,
2536 ti < im->end && ti != -1;
2538 find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2540 /* are we inside the graph ? */
2541 if (ti < im->start || ti > im->end)
2543 while (timajor < ti && timajor != -1) {
2544 timajor = find_next_time(timajor,
2547 mgridtm, im->xlab_user.mgridst);
2549 if (timajor == -1) break; /* fail in case of problems with time increments */
2551 continue; /* skip as falls on major grid line */
2553 gfx_line(im, X0, Y1 - 2, X0, Y1,
2554 GRIDWIDTH, im->graph_col[GRC_GRID]);
2555 gfx_line(im, X0, Y0, X0, Y0 + 2,
2556 GRIDWIDTH, im->graph_col[GRC_GRID]);
2557 gfx_dashed_line(im, X0, Y0 + 1, X0,
2560 graph_col[GRC_GRID],
2561 im->grid_dash_on, im->grid_dash_off);
2565 /* paint the major grid */
2566 for (ti = find_first_time(im->start,
2573 ti < im->end && ti != -1;
2574 ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2576 /* are we inside the graph ? */
2577 if (ti < im->start || ti > im->end)
2580 gfx_line(im, X0, Y1 - 2, X0, Y1,
2581 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2582 gfx_line(im, X0, Y0, X0, Y0 + 3,
2583 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2584 gfx_dashed_line(im, X0, Y0 + 3, X0,
2588 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2590 /* paint the labels below the graph */
2592 find_first_time(im->start -
2601 im->xlab_user.precis / 2) && ti != -1;
2602 ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2604 tilab = ti + im->xlab_user.precis / 2; /* correct time for the label */
2605 /* are we inside the graph ? */
2606 if (tilab < im->start || tilab > im->end)
2609 localtime_r(&tilab, &tm);
2610 strftime(graph_label, 99, im->xlab_user.stst, &tm);
2612 # error "your libc has no strftime I guess we'll abort the exercise here."
2617 im->graph_col[GRC_FONT],
2619 text_prop[TEXT_PROP_AXIS].
2622 GFX_H_CENTER, GFX_V_TOP, graph_label);
2631 /* draw x and y axis */
2632 /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2633 im->xorigin+im->xsize,im->yorigin-im->ysize,
2634 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2636 gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2637 im->xorigin+im->xsize,im->yorigin-im->ysize,
2638 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2640 gfx_line(im, im->xorigin - 4,
2642 im->xorigin + im->xsize +
2643 4, im->yorigin, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2644 gfx_line(im, im->xorigin,
2647 im->yorigin - im->ysize -
2648 4, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2649 /* arrow for X and Y axis direction */
2650 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 */
2651 im->graph_col[GRC_ARROW]);
2653 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 */
2654 im->graph_col[GRC_ARROW]);
2656 if (im->second_axis_scale != 0){
2657 gfx_line ( im, im->xorigin+im->xsize,im->yorigin+4,
2658 im->xorigin+im->xsize,im->yorigin-im->ysize-4,
2659 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2661 im->xorigin+im->xsize-2, im->yorigin-im->ysize-2,
2662 im->xorigin+im->xsize+3, im->yorigin-im->ysize-2,
2663 im->xorigin+im->xsize, im->yorigin-im->ysize-7, /* LINEOFFSET */
2664 im->graph_col[GRC_ARROW]);
2675 double X0, Y0; /* points for filled graph and more */
2676 struct gfx_color_t water_color;
2678 if (im->draw_3d_border > 0) {
2679 /* draw 3d border */
2680 i = im->draw_3d_border;
2681 gfx_new_area(im, 0, im->yimg,
2682 i, im->yimg - i, i, i, im->graph_col[GRC_SHADEA]);
2683 gfx_add_point(im, im->ximg - i, i);
2684 gfx_add_point(im, im->ximg, 0);
2685 gfx_add_point(im, 0, 0);
2687 gfx_new_area(im, i, im->yimg - i,
2689 im->yimg - i, im->ximg - i, i, im->graph_col[GRC_SHADEB]);
2690 gfx_add_point(im, im->ximg, 0);
2691 gfx_add_point(im, im->ximg, im->yimg);
2692 gfx_add_point(im, 0, im->yimg);
2695 if (im->draw_x_grid == 1)
2697 if (im->draw_y_grid == 1) {
2698 if (im->logarithmic) {
2699 res = horizontal_log_grid(im);
2701 res = draw_horizontal_grid(im);
2704 /* dont draw horizontal grid if there is no min and max val */
2706 char *nodata = "No Data found";
2708 gfx_text(im, im->ximg / 2,
2711 im->graph_col[GRC_FONT],
2713 text_prop[TEXT_PROP_AXIS].
2716 GFX_H_CENTER, GFX_V_CENTER, nodata);
2720 /* yaxis unit description */
2721 if (im->ylegend[0] != '\0'){
2723 im->xOriginLegendY+10,
2725 im->graph_col[GRC_FONT],
2727 text_prop[TEXT_PROP_UNIT].
2730 RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2733 if (im->second_axis_legend[0] != '\0'){
2735 im->xOriginLegendY2+10,
2736 im->yOriginLegendY2,
2737 im->graph_col[GRC_FONT],
2738 im->text_prop[TEXT_PROP_UNIT].font_desc,
2740 RRDGRAPH_YLEGEND_ANGLE,
2741 GFX_H_CENTER, GFX_V_CENTER,
2742 im->second_axis_legend);
2747 im->xOriginTitle, im->yOriginTitle+6,
2748 im->graph_col[GRC_FONT],
2750 text_prop[TEXT_PROP_TITLE].
2752 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP, im->title);
2753 /* rrdtool 'logo' */
2754 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
2755 water_color = im->graph_col[GRC_FONT];
2756 water_color.alpha = 0.3;
2757 double xpos = im->legendposition == EAST ? im->xOriginLegendY : im->ximg - 4;
2758 gfx_text(im, xpos, 5,
2761 text_prop[TEXT_PROP_WATERMARK].
2762 font_desc, im->tabwidth,
2763 -90, GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2765 /* graph watermark */
2766 if (im->watermark[0] != '\0') {
2767 water_color = im->graph_col[GRC_FONT];
2768 water_color.alpha = 0.3;
2770 im->ximg / 2, im->yimg - 6,
2773 text_prop[TEXT_PROP_WATERMARK].
2774 font_desc, im->tabwidth, 0,
2775 GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2779 if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
2780 for (i = 0; i < im->gdes_c; i++) {
2781 if (im->gdes[i].legend[0] == '\0')
2783 /* im->gdes[i].leg_y is the bottom of the legend */
2784 X0 = im->xOriginLegend + im->gdes[i].leg_x;
2785 Y0 = im->legenddirection == TOP_DOWN ? im->yOriginLegend + im->gdes[i].leg_y : im->yOriginLegend + im->legendheight - im->gdes[i].leg_y;
2786 gfx_text(im, X0, Y0,
2787 im->graph_col[GRC_FONT],
2790 [TEXT_PROP_LEGEND].font_desc,
2792 GFX_H_LEFT, GFX_V_BOTTOM, im->gdes[i].legend);
2793 /* The legend for GRAPH items starts with "M " to have
2794 enough space for the box */
2795 if (im->gdes[i].gf != GF_PRINT &&
2796 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2800 boxH = gfx_get_text_width(im, 0,
2805 im->tabwidth, "o") * 1.2;
2807 /* shift the box up a bit */
2810 if (im->dynamic_labels && im->gdes[i].gf == GF_HRULE) { /* [-] */
2812 cairo_new_path(im->cr);
2813 cairo_set_line_width(im->cr, 1.0);
2816 X0 + boxH, Y0 - boxV / 2,
2817 1.0, im->gdes[i].col);
2819 } else if (im->dynamic_labels && im->gdes[i].gf == GF_VRULE) { /* [|] */
2821 cairo_new_path(im->cr);
2822 cairo_set_line_width(im->cr, 1.0);
2825 X0 + boxH / 2, Y0 - boxV,
2826 1.0, im->gdes[i].col);
2828 } else if (im->dynamic_labels && im->gdes[i].gf == GF_LINE) { /* [/] */
2830 cairo_new_path(im->cr);
2831 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
2834 X0 + boxH, Y0 - boxV,
2835 im->gdes[i].linewidth, im->gdes[i].col);
2838 /* make sure transparent colors show up the same way as in the graph */
2841 X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2842 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2844 gfx_new_area(im, X0, Y0 - boxV, X0,
2845 Y0, X0 + boxH, Y0, im->gdes[i].col);
2846 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2849 cairo_new_path(im->cr);
2850 cairo_set_line_width(im->cr, 1.0);
2853 gfx_line_fit(im, &X0, &Y0);
2854 gfx_line_fit(im, &X1, &Y1);
2855 cairo_move_to(im->cr, X0, Y0);
2856 cairo_line_to(im->cr, X1, Y0);
2857 cairo_line_to(im->cr, X1, Y1);
2858 cairo_line_to(im->cr, X0, Y1);
2859 cairo_close_path(im->cr);
2860 cairo_set_source_rgba(im->cr,
2861 im->graph_col[GRC_FRAME].red,
2862 im->graph_col[GRC_FRAME].green,
2863 im->graph_col[GRC_FRAME].blue,
2864 im->graph_col[GRC_FRAME].alpha);
2866 if (im->gdes[i].dash) {
2867 /* make box borders in legend dashed if the graph is dashed */
2871 cairo_set_dash(im->cr, dashes, 1, 0.0);
2873 cairo_stroke(im->cr);
2874 cairo_restore(im->cr);
2881 /*****************************************************
2882 * lazy check make sure we rely need to create this graph
2883 *****************************************************/
2890 struct stat imgstat;
2893 return 0; /* no lazy option */
2894 if (strlen(im->graphfile) == 0)
2895 return 0; /* inmemory option */
2896 if (stat(im->graphfile, &imgstat) != 0)
2897 return 0; /* can't stat */
2898 /* one pixel in the existing graph is more then what we would
2900 if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2902 if ((fd = fopen(im->graphfile, "rb")) == NULL)
2903 return 0; /* the file does not exist */
2904 switch (im->imgformat) {
2906 size = PngSize(fd, &(im->ximg), &(im->yimg));
2916 int graph_size_location(
2921 /* The actual size of the image to draw is determined from
2922 ** several sources. The size given on the command line is
2923 ** the graph area but we need more as we have to draw labels
2924 ** and other things outside the graph area. If the option
2925 ** --full-size-mode is selected the size defines the total
2926 ** image size and the size available for the graph is
2930 /** +---+-----------------------------------+
2931 ** | y |...............graph title.........|
2932 ** | +---+-------------------------------+
2936 ** | s | x | main graph area |
2941 ** | l | b +-------------------------------+
2942 ** | e | l | x axis labels |
2943 ** +---+---+-------------------------------+
2944 ** |....................legends............|
2945 ** +---------------------------------------+
2947 ** +---------------------------------------+
2950 int Xvertical = 0, Xvertical2 = 0, Ytitle =
2951 0, Xylabel = 0, Xmain = 0, Ymain =
2952 0, Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2954 // no legends and no the shall be plotted it's easy
2955 if (im->extra_flags & ONLY_GRAPH) {
2957 im->ximg = im->xsize;
2958 im->yimg = im->ysize;
2959 im->yorigin = im->ysize;
2965 if(im->watermark[0] != '\0') {
2966 Ywatermark = im->text_prop[TEXT_PROP_WATERMARK].size * 2;
2969 // calculate the width of the left vertical legend
2970 if (im->ylegend[0] != '\0') {
2971 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2974 // calculate the width of the right vertical legend
2975 if (im->second_axis_legend[0] != '\0') {
2976 Xvertical2 = im->text_prop[TEXT_PROP_UNIT].size * 2;
2979 Xvertical2 = Xspacing;
2982 if (im->title[0] != '\0') {
2983 /* The title is placed "inbetween" two text lines so it
2984 ** automatically has some vertical spacing. The horizontal
2985 ** spacing is added here, on each side.
2987 /* if necessary, reduce the font size of the title until it fits the image width */
2988 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2991 // we have no title; get a little clearing from the top
2996 if (im->draw_x_grid) {
2997 // calculate the height of the horizontal labelling
2998 Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
3000 if (im->draw_y_grid || im->forceleftspace) {
3001 // calculate the width of the vertical labelling
3003 gfx_get_text_width(im, 0,
3004 im->text_prop[TEXT_PROP_AXIS].font_desc,
3005 im->tabwidth, "0") * im->unitslength;
3009 // add some space to the labelling
3010 Xylabel += Xspacing;
3012 /* If the legend is printed besides the graph the width has to be
3013 ** calculated first. Placing the legend north or south of the
3014 ** graph requires the width calculation first, so the legend is
3015 ** skipped for the moment.
3017 im->legendheight = 0;
3018 im->legendwidth = 0;
3019 if (!(im->extra_flags & NOLEGEND)) {
3020 if(im->legendposition == WEST || im->legendposition == EAST){
3021 if (leg_place(im, 1) == -1){
3027 if (im->extra_flags & FULL_SIZE_MODE) {
3029 /* The actual size of the image to draw has been determined by the user.
3030 ** The graph area is the space remaining after accounting for the legend,
3031 ** the watermark, the axis labels, and the title.
3033 im->ximg = im->xsize;
3034 im->yimg = im->ysize;
3038 /* Now calculate the total size. Insert some spacing where
3039 desired. im->xorigin and im->yorigin need to correspond
3040 with the lower left corner of the main graph area or, if
3041 this one is not set, the imaginary box surrounding the
3043 /* Initial size calculation for the main graph area */
3045 Xmain -= Xylabel;// + Xspacing;
3046 if((im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
3047 Xmain -= im->legendwidth;// + Xspacing;
3049 if (im->second_axis_scale != 0){
3052 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3056 Xmain -= Xvertical + Xvertical2;
3058 /* limit the remaining space to 0 */
3064 /* Putting the legend north or south, the height can now be calculated */
3065 if (!(im->extra_flags & NOLEGEND)) {
3066 if(im->legendposition == NORTH || im->legendposition == SOUTH){
3067 im->legendwidth = im->ximg;
3068 if (leg_place(im, 0) == -1){
3074 if( (im->legendposition == NORTH || im->legendposition == SOUTH) && !(im->extra_flags & NOLEGEND) ){
3075 Ymain -= Yxlabel + im->legendheight;
3081 /* reserve space for the title *or* some padding above the graph */
3084 /* reserve space for padding below the graph */
3085 if (im->extra_flags & NOLEGEND) {
3086 Ymain -= 0.5*Yspacing;
3089 if (im->watermark[0] != '\0') {
3090 Ymain -= Ywatermark;
3092 /* limit the remaining height to 0 */
3097 } else { /* dimension options -width and -height refer to the dimensions of the main graph area */
3099 /* The actual size of the image to draw is determined from
3100 ** several sources. The size given on the command line is
3101 ** the graph area but we need more as we have to draw labels
3102 ** and other things outside the graph area.
3106 Xmain = im->xsize; // + Xspacing;
3110 im->ximg = Xmain + Xylabel;
3111 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3112 im->ximg += Xspacing;
3115 if( (im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
3116 im->ximg += im->legendwidth;// + Xspacing;
3118 if (im->second_axis_scale != 0){
3119 im->ximg += Xylabel;
3122 im->ximg += Xvertical + Xvertical2;
3124 if (!(im->extra_flags & NOLEGEND)) {
3125 if(im->legendposition == NORTH || im->legendposition == SOUTH){
3126 im->legendwidth = im->ximg;
3127 if (leg_place(im, 0) == -1){
3133 im->yimg = Ymain + Yxlabel;
3134 if( (im->legendposition == NORTH || im->legendposition == SOUTH) && !(im->extra_flags & NOLEGEND) ){
3135 im->yimg += im->legendheight;
3138 /* reserve space for the title *or* some padding above the graph */
3142 im->yimg += 1.5 * Yspacing;
3144 /* reserve space for padding below the graph */
3145 if (im->extra_flags & NOLEGEND) {
3146 im->yimg += 0.5*Yspacing;
3149 if (im->watermark[0] != '\0') {
3150 im->yimg += Ywatermark;
3155 /* In case of putting the legend in west or east position the first
3156 ** legend calculation might lead to wrong positions if some items
3157 ** are not aligned on the left hand side (e.g. centered) as the
3158 ** legendwidth wight have been increased after the item was placed.
3159 ** In this case the positions have to be recalculated.
3161 if (!(im->extra_flags & NOLEGEND)) {
3162 if(im->legendposition == WEST || im->legendposition == EAST){
3163 if (leg_place(im, 0) == -1){
3169 /* After calculating all dimensions
3170 ** it is now possible to calculate
3173 switch(im->legendposition){
3175 im->xOriginTitle = (im->ximg / 2);
3176 im->yOriginTitle = 0;
3178 im->xOriginLegend = 0;
3179 im->yOriginLegend = Ytitle;
3181 im->xOriginLegendY = 0;
3182 im->yOriginLegendY = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3184 im->xorigin = Xvertical + Xylabel;
3185 im->yorigin = Ytitle + im->legendheight + Ymain;
3187 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3188 if (im->second_axis_scale != 0){
3189 im->xOriginLegendY2 += Xylabel;
3191 im->yOriginLegendY2 = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3196 im->xOriginTitle = im->legendwidth + im->xsize / 2;
3197 im->yOriginTitle = 0;
3199 im->xOriginLegend = 0;
3200 im->yOriginLegend = Ytitle;
3202 im->xOriginLegendY = im->legendwidth;
3203 im->yOriginLegendY = Ytitle + (Ymain / 2);
3205 im->xorigin = im->legendwidth + Xvertical + Xylabel;
3206 im->yorigin = Ytitle + Ymain;
3208 im->xOriginLegendY2 = im->legendwidth + Xvertical + Xylabel + Xmain;
3209 if (im->second_axis_scale != 0){
3210 im->xOriginLegendY2 += Xylabel;
3212 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3217 im->xOriginTitle = im->ximg / 2;
3218 im->yOriginTitle = 0;
3220 im->xOriginLegend = 0;
3221 im->yOriginLegend = Ytitle + Ymain + Yxlabel;
3223 im->xOriginLegendY = 0;
3224 im->yOriginLegendY = Ytitle + (Ymain / 2);
3226 im->xorigin = Xvertical + Xylabel;
3227 im->yorigin = Ytitle + Ymain;
3229 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3230 if (im->second_axis_scale != 0){
3231 im->xOriginLegendY2 += Xylabel;
3233 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3238 im->xOriginTitle = im->xsize / 2;
3239 im->yOriginTitle = 0;
3241 im->xOriginLegend = Xvertical + Xylabel + Xmain + Xvertical2;
3242 if (im->second_axis_scale != 0){
3243 im->xOriginLegend += Xylabel;
3245 im->yOriginLegend = Ytitle;
3247 im->xOriginLegendY = 0;
3248 im->yOriginLegendY = Ytitle + (Ymain / 2);
3250 im->xorigin = Xvertical + Xylabel;
3251 im->yorigin = Ytitle + Ymain;
3253 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3254 if (im->second_axis_scale != 0){
3255 im->xOriginLegendY2 += Xylabel;
3257 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3259 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3260 im->xOriginTitle += Xspacing;
3261 im->xOriginLegend += Xspacing;
3262 im->xOriginLegendY += Xspacing;
3263 im->xorigin += Xspacing;
3264 im->xOriginLegendY2 += Xspacing;
3274 static cairo_status_t cairo_output(
3278 unsigned int length)
3280 image_desc_t *im = (image_desc_t*)closure;
3282 im->rendered_image =
3283 (unsigned char*)realloc(im->rendered_image, im->rendered_image_size + length);
3284 if (im->rendered_image == NULL)
3285 return CAIRO_STATUS_WRITE_ERROR;
3286 memcpy(im->rendered_image + im->rendered_image_size, data, length);
3287 im->rendered_image_size += length;
3288 return CAIRO_STATUS_SUCCESS;
3291 /* draw that picture thing ... */
3296 int lazy = lazy_check(im);
3297 double areazero = 0.0;
3298 graph_desc_t *lastgdes = NULL;
3301 // PangoFontMap *font_map = pango_cairo_font_map_get_default();
3303 /* pull the data from the rrd files ... */
3304 if (data_fetch(im) == -1)
3306 /* evaluate VDEF and CDEF operations ... */
3307 if (data_calc(im) == -1)
3309 /* calculate and PRINT and GPRINT definitions. We have to do it at
3310 * this point because it will affect the length of the legends
3311 * if there are no graph elements (i==0) we stop here ...
3312 * if we are lazy, try to quit ...
3318 /* if we want and can be lazy ... quit now */
3322 /**************************************************************
3323 *** Calculating sizes and locations became a bit confusing ***
3324 *** so I moved this into a separate function. ***
3325 **************************************************************/
3326 if (graph_size_location(im, i) == -1)
3329 info.u_cnt = im->xorigin;
3330 grinfo_push(im, sprintf_alloc("graph_left"), RD_I_CNT, info);
3331 info.u_cnt = im->yorigin - im->ysize;
3332 grinfo_push(im, sprintf_alloc("graph_top"), RD_I_CNT, info);
3333 info.u_cnt = im->xsize;
3334 grinfo_push(im, sprintf_alloc("graph_width"), RD_I_CNT, info);
3335 info.u_cnt = im->ysize;
3336 grinfo_push(im, sprintf_alloc("graph_height"), RD_I_CNT, info);
3337 info.u_cnt = im->ximg;
3338 grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
3339 info.u_cnt = im->yimg;
3340 grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
3341 info.u_cnt = im->start;
3342 grinfo_push(im, sprintf_alloc("graph_start"), RD_I_CNT, info);
3343 info.u_cnt = im->end;
3344 grinfo_push(im, sprintf_alloc("graph_end"), RD_I_CNT, info);
3346 /* if we want and can be lazy ... quit now */
3350 /* get actual drawing data and find min and max values */
3351 if (data_proc(im) == -1)
3353 if (!im->logarithmic) {
3357 /* identify si magnitude Kilo, Mega Giga ? */
3358 if (!im->rigid && !im->logarithmic)
3359 expand_range(im); /* make sure the upper and lower limit are
3362 info.u_val = im->minval;
3363 grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
3364 info.u_val = im->maxval;
3365 grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
3368 if (!calc_horizontal_grid(im))
3373 apply_gridfit(im); */
3374 /* the actual graph is created by going through the individual
3375 graph elements and then drawing them */
3376 cairo_surface_destroy(im->surface);
3377 switch (im->imgformat) {
3380 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3381 im->ximg * im->zoom,
3382 im->yimg * im->zoom);
3386 im->surface = strlen(im->graphfile)
3387 ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3388 im->yimg * im->zoom)
3389 : cairo_pdf_surface_create_for_stream
3390 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3394 im->surface = strlen(im->graphfile)
3396 cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3397 im->yimg * im->zoom)
3398 : cairo_ps_surface_create_for_stream
3399 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3403 im->surface = strlen(im->graphfile)
3405 cairo_svg_surface_create(im->
3407 im->ximg * im->zoom, im->yimg * im->zoom)
3408 : cairo_svg_surface_create_for_stream
3409 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3410 cairo_svg_surface_restrict_to_version
3411 (im->surface, CAIRO_SVG_VERSION_1_1);
3420 cairo_destroy(im->cr);
3421 im->cr = cairo_create(im->surface);
3422 cairo_set_antialias(im->cr, im->graph_antialias);
3423 cairo_scale(im->cr, im->zoom, im->zoom);
3424 // pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3425 gfx_new_area(im, 0, 0, 0, im->yimg,
3426 im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3427 gfx_add_point(im, im->ximg, 0);
3429 gfx_new_area(im, im->xorigin,
3432 im->xsize, im->yorigin,
3435 im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3436 gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3438 cairo_rectangle(im->cr, im->xorigin, im->yorigin - im->ysize - 1.0,
3439 im->xsize, im->ysize + 2.0);
3441 if (im->minval > 0.0)
3442 areazero = im->minval;
3443 if (im->maxval < 0.0)
3444 areazero = im->maxval;
3445 for (i = 0; i < im->gdes_c; i++) {
3446 switch (im->gdes[i].gf) {
3460 for (ii = 0; ii < im->xsize; ii++) {
3461 if (!isnan(im->gdes[i].p_data[ii])
3462 && im->gdes[i].p_data[ii] != 0.0) {
3463 if (im->gdes[i].yrule > 0) {
3470 im->ysize, 1.0, im->gdes[i].col);
3471 } else if (im->gdes[i].yrule < 0) {
3474 im->yorigin - im->ysize - 1.0,
3476 im->yorigin - im->ysize -
3479 im->ysize, 1.0, im->gdes[i].col);
3487 rrd_value_t diffval = im->maxval - im->minval;
3488 rrd_value_t maxlimit = im->maxval + 9 * diffval;
3489 rrd_value_t minlimit = im->minval - 9 * diffval;
3490 for (ii = 0; ii < im->xsize; ii++) {
3491 /* fix data points at oo and -oo */
3492 if (isinf(im->gdes[i].p_data[ii])) {
3493 if (im->gdes[i].p_data[ii] > 0) {
3494 im->gdes[i].p_data[ii] = im->maxval;
3496 im->gdes[i].p_data[ii] = im->minval;
3499 /* some versions of cairo go unstable when trying
3500 to draw way out of the canvas ... lets not even try */
3501 if (im->gdes[i].p_data[ii] > maxlimit) {
3502 im->gdes[i].p_data[ii] = maxlimit;
3504 if (im->gdes[i].p_data[ii] < minlimit) {
3505 im->gdes[i].p_data[ii] = minlimit;
3509 /* *******************************************************
3514 -------|--t-1--t--------------------------------
3516 if we know the value at time t was a then
3517 we draw a square from t-1 to t with the value a.
3519 ********************************************************* */
3520 if (im->gdes[i].col.alpha != 0.0) {
3521 /* GF_LINE and friend */
3522 if (im->gdes[i].gf == GF_LINE) {
3523 double last_y = 0.0;
3527 cairo_new_path(im->cr);
3528 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3529 if (im->gdes[i].dash) {
3530 cairo_set_dash(im->cr,
3531 im->gdes[i].p_dashes,
3532 im->gdes[i].ndash, im->gdes[i].offset);
3535 for (ii = 1; ii < im->xsize; ii++) {
3536 if (isnan(im->gdes[i].p_data[ii])
3537 || (im->slopemode == 1
3538 && isnan(im->gdes[i].p_data[ii - 1]))) {
3543 last_y = ytr(im, im->gdes[i].p_data[ii]);
3544 if (im->slopemode == 0) {
3545 double x = ii - 1 + im->xorigin;
3548 gfx_line_fit(im, &x, &y);
3549 cairo_move_to(im->cr, x, y);
3550 x = ii + im->xorigin;
3552 gfx_line_fit(im, &x, &y);
3553 cairo_line_to(im->cr, x, y);
3555 double x = ii - 1 + im->xorigin;
3557 ytr(im, im->gdes[i].p_data[ii - 1]);
3558 gfx_line_fit(im, &x, &y);
3559 cairo_move_to(im->cr, x, y);
3560 x = ii + im->xorigin;
3562 gfx_line_fit(im, &x, &y);
3563 cairo_line_to(im->cr, x, y);
3567 double x1 = ii + im->xorigin;
3568 double y1 = ytr(im, im->gdes[i].p_data[ii]);
3570 if (im->slopemode == 0
3571 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3572 double x = ii - 1 + im->xorigin;
3575 gfx_line_fit(im, &x, &y);
3576 cairo_line_to(im->cr, x, y);
3579 gfx_line_fit(im, &x1, &y1);
3580 cairo_line_to(im->cr, x1, y1);
3583 cairo_set_source_rgba(im->cr,
3589 col.blue, im->gdes[i].col.alpha);
3590 cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3591 cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3592 cairo_stroke(im->cr);
3593 cairo_restore(im->cr);
3599 (double *) malloc(sizeof(double) * im->xsize * 2);
3601 (double *) malloc(sizeof(double) * im->xsize * 2);
3603 (double *) malloc(sizeof(double) * im->xsize * 2);
3605 (double *) malloc(sizeof(double) * im->xsize * 2);
3608 for (ii = 0; ii <= im->xsize; ii++) {
3611 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3617 AlmostEqual2sComplement(foreY
3621 AlmostEqual2sComplement(foreY
3627 if (im->gdes[i].gf != GF_GRAD) {
3632 foreY[cntI], im->gdes[i].col);
3634 lastx = foreX[cntI];
3635 lasty = foreY[cntI];
3637 while (cntI < idxI) {
3642 AlmostEqual2sComplement(foreY
3646 AlmostEqual2sComplement(foreY
3653 if (im->gdes[i].gf != GF_GRAD) {
3654 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3656 gfx_add_rect_fadey(im,
3658 foreX[cntI], foreY[cntI], lasty,
3661 im->gdes[i].gradheight
3663 lastx = foreX[cntI];
3664 lasty = foreY[cntI];
3667 if (im->gdes[i].gf != GF_GRAD) {
3668 gfx_add_point(im, backX[idxI], backY[idxI]);
3670 gfx_add_rect_fadey(im,
3672 backX[idxI], backY[idxI], lasty,
3675 im->gdes[i].gradheight);
3676 lastx = backX[idxI];
3677 lasty = backY[idxI];
3684 AlmostEqual2sComplement(backY
3688 AlmostEqual2sComplement(backY
3695 if (im->gdes[i].gf != GF_GRAD) {
3696 gfx_add_point(im, backX[idxI], backY[idxI]);
3698 gfx_add_rect_fadey(im,
3700 backX[idxI], backY[idxI], lasty,
3703 im->gdes[i].gradheight);
3704 lastx = backX[idxI];
3705 lasty = backY[idxI];
3710 if (im->gdes[i].gf != GF_GRAD)
3717 if (ii == im->xsize)
3719 if (im->slopemode == 0 && ii == 0) {
3722 if (isnan(im->gdes[i].p_data[ii])) {
3726 ytop = ytr(im, im->gdes[i].p_data[ii]);
3727 if (lastgdes && im->gdes[i].stack) {
3728 ybase = ytr(im, lastgdes->p_data[ii]);
3730 ybase = ytr(im, areazero);
3732 if (ybase == ytop) {
3738 double extra = ytop;
3743 if (im->slopemode == 0) {
3744 backY[++idxI] = ybase - 0.2;
3745 backX[idxI] = ii + im->xorigin - 1;
3746 foreY[idxI] = ytop + 0.2;
3747 foreX[idxI] = ii + im->xorigin - 1;
3749 backY[++idxI] = ybase - 0.2;
3750 backX[idxI] = ii + im->xorigin;
3751 foreY[idxI] = ytop + 0.2;
3752 foreX[idxI] = ii + im->xorigin;
3754 /* close up any remaining area */
3759 } /* else GF_LINE */
3761 /* if color != 0x0 */
3762 /* make sure we do not run into trouble when stacking on NaN */
3763 for (ii = 0; ii < im->xsize; ii++) {
3764 if (isnan(im->gdes[i].p_data[ii])) {
3765 if (lastgdes && (im->gdes[i].stack)) {
3766 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3768 im->gdes[i].p_data[ii] = areazero;
3772 lastgdes = &(im->gdes[i]);
3774 } /* GF_AREA, GF_LINE, GF_GRAD */
3777 ("STACK should already be turned into LINE or AREA here");
3782 cairo_reset_clip(im->cr);
3784 /* grid_paint also does the text */
3785 if (!(im->extra_flags & ONLY_GRAPH))
3787 if (!(im->extra_flags & ONLY_GRAPH))
3789 /* the RULES are the last thing to paint ... */
3790 for (i = 0; i < im->gdes_c; i++) {
3792 switch (im->gdes[i].gf) {
3794 if (im->gdes[i].yrule >= im->minval
3795 && im->gdes[i].yrule <= im->maxval) {
3797 if (im->gdes[i].dash) {
3798 cairo_set_dash(im->cr,
3799 im->gdes[i].p_dashes,
3800 im->gdes[i].ndash, im->gdes[i].offset);
3802 gfx_line(im, im->xorigin,
3803 ytr(im, im->gdes[i].yrule),
3804 im->xorigin + im->xsize,
3805 ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
3806 cairo_stroke(im->cr);
3807 cairo_restore(im->cr);
3811 if (im->gdes[i].xrule >= im->start
3812 && im->gdes[i].xrule <= im->end) {
3814 if (im->gdes[i].dash) {
3815 cairo_set_dash(im->cr,
3816 im->gdes[i].p_dashes,
3817 im->gdes[i].ndash, im->gdes[i].offset);
3820 xtr(im, im->gdes[i].xrule),
3821 im->yorigin, xtr(im,
3825 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3826 cairo_stroke(im->cr);
3827 cairo_restore(im->cr);
3836 switch (im->imgformat) {
3839 cairo_status_t status;
3841 status = strlen(im->graphfile) ?
3842 cairo_surface_write_to_png(im->surface, im->graphfile)
3843 : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
3846 if (status != CAIRO_STATUS_SUCCESS) {
3847 rrd_set_error("Could not save png to '%s'", im->graphfile);
3853 if (strlen(im->graphfile)) {
3854 cairo_show_page(im->cr);
3856 cairo_surface_finish(im->surface);
3865 /*****************************************************
3867 *****************************************************/
3874 if ((im->gdes = (graph_desc_t *)
3875 rrd_realloc(im->gdes, (im->gdes_c)
3876 * sizeof(graph_desc_t))) == NULL) {
3877 rrd_set_error("realloc graph_descs");
3882 im->gdes[im->gdes_c - 1].step = im->step;
3883 im->gdes[im->gdes_c - 1].step_orig = im->step;
3884 im->gdes[im->gdes_c - 1].stack = 0;
3885 im->gdes[im->gdes_c - 1].linewidth = 0;
3886 im->gdes[im->gdes_c - 1].debug = 0;
3887 im->gdes[im->gdes_c - 1].start = im->start;
3888 im->gdes[im->gdes_c - 1].start_orig = im->start;
3889 im->gdes[im->gdes_c - 1].end = im->end;
3890 im->gdes[im->gdes_c - 1].end_orig = im->end;
3891 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3892 im->gdes[im->gdes_c - 1].data = NULL;
3893 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3894 im->gdes[im->gdes_c - 1].data_first = 0;
3895 im->gdes[im->gdes_c - 1].p_data = NULL;
3896 im->gdes[im->gdes_c - 1].rpnp = NULL;
3897 im->gdes[im->gdes_c - 1].p_dashes = NULL;
3898 im->gdes[im->gdes_c - 1].shift = 0.0;
3899 im->gdes[im->gdes_c - 1].dash = 0;
3900 im->gdes[im->gdes_c - 1].ndash = 0;
3901 im->gdes[im->gdes_c - 1].offset = 0;
3902 im->gdes[im->gdes_c - 1].col.red = 0.0;
3903 im->gdes[im->gdes_c - 1].col.green = 0.0;
3904 im->gdes[im->gdes_c - 1].col.blue = 0.0;
3905 im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3906 im->gdes[im->gdes_c - 1].col2.red = 0.0;
3907 im->gdes[im->gdes_c - 1].col2.green = 0.0;
3908 im->gdes[im->gdes_c - 1].col2.blue = 0.0;
3909 im->gdes[im->gdes_c - 1].col2.alpha = 0.0;
3910 im->gdes[im->gdes_c - 1].gradheight = 50.0;
3911 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3912 im->gdes[im->gdes_c - 1].format[0] = '\0';
3913 im->gdes[im->gdes_c - 1].strftm = 0;
3914 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3915 im->gdes[im->gdes_c - 1].ds = -1;
3916 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3917 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3918 im->gdes[im->gdes_c - 1].yrule = DNAN;
3919 im->gdes[im->gdes_c - 1].xrule = 0;
3920 im->gdes[im->gdes_c - 1].daemon[0] = 0;
3924 /* copies input untill the first unescaped colon is found
3925 or until input ends. backslashes have to be escaped as well */
3927 const char *const input,
3933 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3934 if (input[inp] == '\\'
3935 && input[inp + 1] != '\0'
3936 && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3937 output[outp++] = input[++inp];
3939 output[outp++] = input[inp];
3942 output[outp] = '\0';
3946 /* Now just a wrapper around rrd_graph_v */
3958 rrd_info_t *grinfo = NULL;
3961 grinfo = rrd_graph_v(argc, argv);
3967 if (strcmp(walker->key, "image_info") == 0) {
3970 (char**)rrd_realloc((*prdata),
3971 (prlines + 1) * sizeof(char *))) == NULL) {
3972 rrd_set_error("realloc prdata");
3975 /* imginfo goes to position 0 in the prdata array */
3976 (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3977 + 2) * sizeof(char));
3978 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3979 (*prdata)[prlines] = NULL;
3981 /* skip anything else */
3982 walker = walker->next;
3990 if (strcmp(walker->key, "image_width") == 0) {
3991 *xsize = walker->value.u_cnt;
3992 } else if (strcmp(walker->key, "image_height") == 0) {
3993 *ysize = walker->value.u_cnt;
3994 } else if (strcmp(walker->key, "value_min") == 0) {
3995 *ymin = walker->value.u_val;
3996 } else if (strcmp(walker->key, "value_max") == 0) {
3997 *ymax = walker->value.u_val;
3998 } else if (strncmp(walker->key, "print", 5) == 0) { /* keys are prdate[0..] */
4001 (char**)rrd_realloc((*prdata),
4002 (prlines + 1) * sizeof(char *))) == NULL) {
4003 rrd_set_error("realloc prdata");
4006 (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
4007 + 2) * sizeof(char));
4008 (*prdata)[prlines] = NULL;
4009 strcpy((*prdata)[prlines - 1], walker->value.u_str);
4010 } else if (strcmp(walker->key, "image") == 0) {
4011 if ( fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
4012 (stream ? stream : stdout)) == 0 && ferror(stream ? stream : stdout)){
4013 rrd_set_error("writing image");
4017 /* skip anything else */
4018 walker = walker->next;
4020 rrd_info_free(grinfo);
4025 /* Some surgery done on this function, it became ridiculously big.
4027 ** - initializing now in rrd_graph_init()
4028 ** - options parsing now in rrd_graph_options()
4029 ** - script parsing now in rrd_graph_script()
4032 /* have no better idea where to put it - rrd.h does not work */
4033 int rrd_graph_xport(image_desc_t *);
4035 rrd_info_t *rrd_graph_v(
4042 rrd_graph_init(&im);
4043 /* a dummy surface so that we can measure text sizes for placements */
4044 old_locale = setlocale(LC_NUMERIC, NULL);
4045 setlocale(LC_NUMERIC, "C");
4046 rrd_graph_options(argc, argv, &im);
4047 if (rrd_test_error()) {
4048 setlocale(LC_NUMERIC, old_locale); /* reenable locale */
4049 rrd_info_free(im.grinfo);
4054 if (optind >= argc) {
4055 setlocale(LC_NUMERIC, old_locale); /* reenable locale */
4056 rrd_info_free(im.grinfo);
4058 rrd_set_error("missing filename");
4062 if (strlen(argv[optind]) >= MAXPATH) {
4063 setlocale(LC_NUMERIC, old_locale); /* reenable locale */
4064 rrd_set_error("filename (including path) too long");
4065 rrd_info_free(im.grinfo);
4070 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
4071 im.graphfile[MAXPATH - 1] = '\0';
4073 if (strcmp(im.graphfile, "-") == 0) {
4074 im.graphfile[0] = '\0';
4077 rrd_graph_script(argc, argv, &im, 1);
4078 setlocale(LC_NUMERIC, old_locale); /* reenable locale for rendering the graph */
4080 if (rrd_test_error()) {
4081 rrd_info_free(im.grinfo);
4086 if (im.imgformat >= IF_XML) {
4087 rrd_graph_xport(&im);
4090 /* Everything is now read and the actual work can start */
4092 if (graph_paint(&im) == -1) {
4093 rrd_info_free(im.grinfo);
4099 /* The image is generated and needs to be output.
4100 ** Also, if needed, print a line with information about the image.
4108 path = strdup(im.graphfile);
4109 filename = basename(path);
4111 sprintf_alloc(im.imginfo,
4114 im.ximg), (long) (im.zoom * im.yimg));
4115 grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
4119 if (im.rendered_image) {
4122 img.u_blo.size = im.rendered_image_size;
4123 img.u_blo.ptr = im.rendered_image;
4124 grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
4133 image_desc_t *im,int prop,char *font, double size ){
4135 strncpy(im->text_prop[prop].font, font, sizeof(text_prop[prop].font) - 1);
4136 im->text_prop[prop].font[sizeof(text_prop[prop].font) - 1] = '\0';
4137 /* if we already got one, drop it first */
4138 pango_font_description_free(im->text_prop[prop].font_desc);
4139 im->text_prop[prop].font_desc = pango_font_description_from_string( font );
4142 im->text_prop[prop].size = size;
4144 if (im->text_prop[prop].font_desc && im->text_prop[prop].size ){
4145 pango_font_description_set_size(im->text_prop[prop].font_desc, im->text_prop[prop].size * PANGO_SCALE);
4149 void rrd_graph_init(
4154 char *deffont = getenv("RRD_DEFAULT_FONT");
4155 static PangoFontMap *fontmap = NULL;
4156 PangoContext *context;
4163 im->daemon_addr = NULL;
4164 im->draw_x_grid = 1;
4165 im->draw_y_grid = 1;
4166 im->draw_3d_border = 2;
4167 im->dynamic_labels = 0;
4168 im->extra_flags = 0;
4169 im->font_options = cairo_font_options_create();
4170 im->forceleftspace = 0;
4173 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4174 im->grid_dash_off = 1;
4175 im->grid_dash_on = 1;
4177 im->grinfo = (rrd_info_t *) NULL;
4178 im->grinfo_current = (rrd_info_t *) NULL;
4179 im->imgformat = IF_PNG;
4182 im->legenddirection = TOP_DOWN;
4183 im->legendheight = 0;
4184 im->legendposition = SOUTH;
4185 im->legendwidth = 0;
4186 im->logarithmic = 0;
4193 im->rendered_image_size = 0;
4194 im->rendered_image = NULL;
4198 im->tabwidth = 40.0;
4199 im->title[0] = '\0';
4200 im->unitsexponent = 9999;
4201 im->unitslength = 6;
4202 im->viewfactor = 1.0;
4203 im->watermark[0] = '\0';
4204 im->with_markup = 0;
4206 im->xlab_user.minsec = -1;
4208 im->xOriginLegend = 0;
4209 im->xOriginLegendY = 0;
4210 im->xOriginLegendY2 = 0;
4211 im->xOriginTitle = 0;
4213 im->ygridstep = DNAN;
4215 im->ylegend[0] = '\0';
4216 im->second_axis_scale = 0; /* 0 disables it */
4217 im->second_axis_shift = 0; /* no shift by default */
4218 im->second_axis_legend[0] = '\0';
4219 im->second_axis_format[0] = '\0';
4221 im->yOriginLegend = 0;
4222 im->yOriginLegendY = 0;
4223 im->yOriginLegendY2 = 0;
4224 im->yOriginTitle = 0;
4228 im->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
4229 im->cr = cairo_create(im->surface);
4231 for (i = 0; i < DIM(text_prop); i++) {
4232 im->text_prop[i].size = -1;
4233 im->text_prop[i].font_desc = NULL;
4234 rrd_set_font_desc(im,i, deffont ? deffont : text_prop[i].font,text_prop[i].size);
4237 if (fontmap == NULL){
4238 fontmap = pango_cairo_font_map_get_default();
4241 context = pango_cairo_font_map_create_context((PangoCairoFontMap*)fontmap);
4243 pango_cairo_context_set_resolution(context, 100);
4245 pango_cairo_update_context(im->cr,context);
4247 im->layout = pango_layout_new(context);
4248 g_object_unref (context);
4250 // im->layout = pango_cairo_create_layout(im->cr);
4253 cairo_font_options_set_hint_style
4254 (im->font_options, CAIRO_HINT_STYLE_FULL);
4255 cairo_font_options_set_hint_metrics
4256 (im->font_options, CAIRO_HINT_METRICS_ON);
4257 cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
4261 for (i = 0; i < DIM(graph_col); i++)
4262 im->graph_col[i] = graph_col[i];
4268 void rrd_graph_options(
4275 char *parsetime_error = NULL;
4276 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
4277 time_t start_tmp = 0, end_tmp = 0;
4279 rrd_time_value_t start_tv, end_tv;
4280 long unsigned int color;
4282 /* defines for long options without a short equivalent. should be bytes,
4283 and may not collide with (the ASCII value of) short options */
4284 #define LONGOPT_UNITS_SI 255
4287 struct option long_options[] = {
4288 { "alt-autoscale", no_argument, 0, 'A'},
4289 { "imgformat", required_argument, 0, 'a'},
4290 { "font-smoothing-threshold", required_argument, 0, 'B'},
4291 { "base", required_argument, 0, 'b'},
4292 { "color", required_argument, 0, 'c'},
4293 { "full-size-mode", no_argument, 0, 'D'},
4294 { "daemon", required_argument, 0, 'd'},
4295 { "slope-mode", no_argument, 0, 'E'},
4296 { "end", required_argument, 0, 'e'},
4297 { "force-rules-legend", no_argument, 0, 'F'},
4298 { "imginfo", required_argument, 0, 'f'},
4299 { "graph-render-mode", required_argument, 0, 'G'},
4300 { "no-legend", no_argument, 0, 'g'},
4301 { "height", required_argument, 0, 'h'},
4302 { "no-minor", no_argument, 0, 'I'},
4303 { "interlaced", no_argument, 0, 'i'},
4304 { "alt-autoscale-min", no_argument, 0, 'J'},
4305 { "only-graph", no_argument, 0, 'j'},
4306 { "units-length", required_argument, 0, 'L'},
4307 { "lower-limit", required_argument, 0, 'l'},
4308 { "alt-autoscale-max", no_argument, 0, 'M'},
4309 { "zoom", required_argument, 0, 'm'},
4310 { "no-gridfit", no_argument, 0, 'N'},
4311 { "font", required_argument, 0, 'n'},
4312 { "logarithmic", no_argument, 0, 'o'},
4313 { "pango-markup", no_argument, 0, 'P'},
4314 { "font-render-mode", required_argument, 0, 'R'},
4315 { "rigid", no_argument, 0, 'r'},
4316 { "step", required_argument, 0, 'S'},
4317 { "start", required_argument, 0, 's'},
4318 { "tabwidth", required_argument, 0, 'T'},
4319 { "title", required_argument, 0, 't'},
4320 { "upper-limit", required_argument, 0, 'u'},
4321 { "vertical-label", required_argument, 0, 'v'},
4322 { "watermark", required_argument, 0, 'W'},
4323 { "width", required_argument, 0, 'w'},
4324 { "units-exponent", required_argument, 0, 'X'},
4325 { "x-grid", required_argument, 0, 'x'},
4326 { "alt-y-grid", no_argument, 0, 'Y'},
4327 { "y-grid", required_argument, 0, 'y'},
4328 { "lazy", no_argument, 0, 'z'},
4329 { "units", required_argument, 0, LONGOPT_UNITS_SI},
4330 { "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 */
4331 { "disable-rrdtool-tag",no_argument, 0, 1001},
4332 { "right-axis", required_argument, 0, 1002},
4333 { "right-axis-label", required_argument, 0, 1003},
4334 { "right-axis-format", required_argument, 0, 1004},
4335 { "legend-position", required_argument, 0, 1005},
4336 { "legend-direction", required_argument, 0, 1006},
4337 { "border", required_argument, 0, 1007},
4338 { "grid-dash", required_argument, 0, 1008},
4339 { "dynamic-labels", no_argument, 0, 1009},
4340 { "week-fmt", required_argument, 0, 1010},
4346 opterr = 0; /* initialize getopt */
4347 rrd_parsetime("end-24h", &start_tv);
4348 rrd_parsetime("now", &end_tv);
4350 int option_index = 0;
4352 int col_start, col_end;
4354 opt = getopt_long(argc, argv,
4355 "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",
4356 long_options, &option_index);
4361 im->extra_flags |= NOMINOR;
4364 im->extra_flags |= ALTYGRID;
4367 im->extra_flags |= ALTAUTOSCALE;
4370 im->extra_flags |= ALTAUTOSCALE_MIN;
4373 im->extra_flags |= ALTAUTOSCALE_MAX;
4376 im->extra_flags |= ONLY_GRAPH;
4379 im->extra_flags |= NOLEGEND;
4382 if (strcmp(optarg, "north") == 0) {
4383 im->legendposition = NORTH;
4384 } else if (strcmp(optarg, "west") == 0) {
4385 im->legendposition = WEST;
4386 } else if (strcmp(optarg, "south") == 0) {
4387 im->legendposition = SOUTH;
4388 } else if (strcmp(optarg, "east") == 0) {
4389 im->legendposition = EAST;
4391 rrd_set_error("unknown legend-position '%s'", optarg);
4396 if (strcmp(optarg, "topdown") == 0) {
4397 im->legenddirection = TOP_DOWN;
4398 } else if (strcmp(optarg, "bottomup") == 0) {
4399 im->legenddirection = BOTTOM_UP;
4401 rrd_set_error("unknown legend-position '%s'", optarg);
4406 im->extra_flags |= FORCE_RULES_LEGEND;
4409 im->extra_flags |= NO_RRDTOOL_TAG;
4411 case LONGOPT_UNITS_SI:
4412 if (im->extra_flags & FORCE_UNITS) {
4413 rrd_set_error("--units can only be used once!");
4416 if (strcmp(optarg, "si") == 0)
4417 im->extra_flags |= FORCE_UNITS_SI;
4419 rrd_set_error("invalid argument for --units: %s", optarg);
4424 im->unitsexponent = atoi(optarg);
4427 im->unitslength = atoi(optarg);
4428 im->forceleftspace = 1;
4431 im->tabwidth = atof(optarg);
4434 im->step = atoi(optarg);
4440 im->with_markup = 1;
4443 if ((parsetime_error = rrd_parsetime(optarg, &start_tv))) {
4444 rrd_set_error("start time: %s", parsetime_error);
4449 if ((parsetime_error = rrd_parsetime(optarg, &end_tv))) {
4450 rrd_set_error("end time: %s", parsetime_error);
4455 if (strcmp(optarg, "none") == 0) {
4456 im->draw_x_grid = 0;
4460 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
4462 &im->xlab_user.gridst,
4464 &im->xlab_user.mgridst,
4466 &im->xlab_user.labst,
4467 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
4468 strncpy(im->xlab_form, optarg + stroff,
4469 sizeof(im->xlab_form) - 1);
4470 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
4472 (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
4473 rrd_set_error("unknown keyword %s", scan_gtm);
4476 (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
4478 rrd_set_error("unknown keyword %s", scan_mtm);
4481 (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
4482 rrd_set_error("unknown keyword %s", scan_ltm);
4485 im->xlab_user.minsec = 1;
4486 im->xlab_user.stst = im->xlab_form;
4488 rrd_set_error("invalid x-grid format");
4494 if (strcmp(optarg, "none") == 0) {
4495 im->draw_y_grid = 0;
4498 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
4499 if (im->ygridstep <= 0) {
4500 rrd_set_error("grid step must be > 0");
4502 } else if (im->ylabfact < 1) {
4503 rrd_set_error("label factor must be > 0");
4507 rrd_set_error("invalid y-grid format");
4512 im->draw_3d_border = atoi(optarg);
4514 case 1008: /* grid-dash */
4518 &im->grid_dash_off) != 2) {
4519 rrd_set_error("expected grid-dash format float:float");
4523 case 1009: /* enable dynamic labels */
4524 im->dynamic_labels = 1;
4527 strncpy(week_fmt,optarg,sizeof week_fmt);
4528 week_fmt[(sizeof week_fmt)-1]='\0';
4530 case 1002: /* right y axis */
4534 &im->second_axis_scale,
4535 &im->second_axis_shift) == 2) {
4536 if(im->second_axis_scale==0){
4537 rrd_set_error("the second_axis_scale must not be 0");
4541 rrd_set_error("invalid right-axis format expected scale:shift");
4546 strncpy(im->second_axis_legend,optarg,150);
4547 im->second_axis_legend[150]='\0';
4550 if (bad_format(optarg)){
4551 rrd_set_error("use either %le or %lf formats");
4554 strncpy(im->second_axis_format,optarg,150);
4555 im->second_axis_format[150]='\0';
4558 strncpy(im->ylegend, optarg, 150);
4559 im->ylegend[150] = '\0';
4562 im->maxval = atof(optarg);
4565 im->minval = atof(optarg);
4568 im->base = atol(optarg);
4569 if (im->base != 1024 && im->base != 1000) {
4571 ("the only sensible value for base apart from 1000 is 1024");
4576 long_tmp = atol(optarg);
4577 if (long_tmp < 10) {
4578 rrd_set_error("width below 10 pixels");
4581 im->xsize = long_tmp;
4584 long_tmp = atol(optarg);
4585 if (long_tmp < 10) {
4586 rrd_set_error("height below 10 pixels");
4589 im->ysize = long_tmp;
4592 im->extra_flags |= FULL_SIZE_MODE;
4595 /* interlaced png not supported at the moment */
4601 im->imginfo = optarg;
4605 (im->imgformat = if_conv(optarg)) == -1) {
4606 rrd_set_error("unsupported graphics format '%s'", optarg);
4617 im->logarithmic = 1;
4621 "%10[A-Z]#%n%8lx%n",
4622 col_nam, &col_start, &color, &col_end) == 2) {
4624 int col_len = col_end - col_start;
4629 (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4637 (((color & 0xF000) *
4638 0x11000) | ((color & 0x0F00) *
4639 0x01100) | ((color &
4642 ((color & 0x000F) * 0x00011)
4646 color = (color << 8) + 0xff /* shift left by 8 */ ;
4651 rrd_set_error("the color format is #RRGGBB[AA]");
4654 if ((ci = grc_conv(col_nam)) != -1) {
4655 im->graph_col[ci] = gfx_hex_to_col(color);
4657 rrd_set_error("invalid color name '%s'", col_nam);
4661 rrd_set_error("invalid color def format");
4670 if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4671 int sindex, propidx;
4673 if ((sindex = text_prop_conv(prop)) != -1) {
4674 for (propidx = sindex;
4675 propidx < TEXT_PROP_LAST; propidx++) {
4677 rrd_set_font_desc(im,propidx,NULL,size);
4679 if ((int) strlen(optarg) > end+2) {
4680 if (optarg[end] == ':') {
4681 rrd_set_font_desc(im,propidx,optarg + end + 1,0);
4684 ("expected : after font size in '%s'",
4689 /* only run the for loop for DEFAULT (0) for
4690 all others, we break here. woodo programming */
4691 if (propidx == sindex && sindex != 0)
4695 rrd_set_error("invalid fonttag '%s'", prop);
4699 rrd_set_error("invalid text property format");
4705 im->zoom = atof(optarg);
4706 if (im->zoom <= 0.0) {
4707 rrd_set_error("zoom factor must be > 0");
4712 strncpy(im->title, optarg, 150);
4713 im->title[150] = '\0';
4716 if (strcmp(optarg, "normal") == 0) {
4717 cairo_font_options_set_antialias
4718 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4719 cairo_font_options_set_hint_style
4720 (im->font_options, CAIRO_HINT_STYLE_FULL);
4721 } else if (strcmp(optarg, "light") == 0) {
4722 cairo_font_options_set_antialias
4723 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4724 cairo_font_options_set_hint_style
4725 (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4726 } else if (strcmp(optarg, "mono") == 0) {
4727 cairo_font_options_set_antialias
4728 (im->font_options, CAIRO_ANTIALIAS_NONE);
4729 cairo_font_options_set_hint_style
4730 (im->font_options, CAIRO_HINT_STYLE_FULL);
4732 rrd_set_error("unknown font-render-mode '%s'", optarg);
4737 if (strcmp(optarg, "normal") == 0)
4738 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4739 else if (strcmp(optarg, "mono") == 0)
4740 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4742 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4747 /* not supported curently */
4750 strncpy(im->watermark, optarg, 100);
4751 im->watermark[99] = '\0';
4755 if (im->daemon_addr != NULL)
4757 rrd_set_error ("You cannot specify --daemon "
4762 im->daemon_addr = strdup(optarg);
4763 if (im->daemon_addr == NULL)
4765 rrd_set_error("strdup failed");
4773 rrd_set_error("unknown option '%c'", optopt);
4775 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4780 pango_cairo_context_set_font_options(pango_layout_get_context(im->layout), im->font_options);
4781 pango_layout_context_changed(im->layout);
4785 if (im->logarithmic && im->minval <= 0) {
4787 ("for a logarithmic yaxis you must specify a lower-limit > 0");
4791 if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4792 /* error string is set in rrd_parsetime.c */
4796 if (start_tmp < 3600 * 24 * 365 * 10) {
4798 ("the first entry to fetch should be after 1980 (%ld)",
4803 if (end_tmp < start_tmp) {
4805 ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4809 im->start = start_tmp;
4811 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4814 int rrd_graph_color(
4822 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4824 color = strstr(var, "#");
4825 if (color == NULL) {
4826 if (optional == 0) {
4827 rrd_set_error("Found no color in %s", err);
4834 long unsigned int col;
4836 rest = strstr(color, ":");
4843 sscanf(color, "#%6lx%n", &col, &n);
4844 col = (col << 8) + 0xff /* shift left by 8 */ ;
4846 rrd_set_error("Color problem in %s", err);
4849 sscanf(color, "#%8lx%n", &col, &n);
4853 rrd_set_error("Color problem in %s", err);
4855 if (rrd_test_error())
4857 gdp->col = gfx_hex_to_col(col);
4870 while (*ptr != '\0')
4871 if (*ptr++ == '%') {
4873 /* line cannot end with percent char */
4876 /* '%s', '%S' and '%%' are allowed */
4877 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4879 /* %c is allowed (but use only with vdef!) */
4880 else if (*ptr == 'c') {
4885 /* or else '% 6.2lf' and such are allowed */
4887 /* optional padding character */
4888 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4890 /* This should take care of 'm.n' with all three optional */
4891 while (*ptr >= '0' && *ptr <= '9')
4895 while (*ptr >= '0' && *ptr <= '9')
4897 /* Either 'le', 'lf' or 'lg' must follow here */
4900 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4915 const char *const str)
4917 /* A VDEF currently is either "func" or "param,func"
4918 * so the parsing is rather simple. Change if needed.
4925 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4926 if (n == (int) strlen(str)) { /* matched */
4930 sscanf(str, "%29[A-Z]%n", func, &n);
4931 if (n == (int) strlen(str)) { /* matched */
4935 ("Unknown function string '%s' in VDEF '%s'",
4940 if (!strcmp("PERCENT", func))
4941 gdes->vf.op = VDEF_PERCENT;
4942 else if (!strcmp("PERCENTNAN", func))
4943 gdes->vf.op = VDEF_PERCENTNAN;
4944 else if (!strcmp("MAXIMUM", func))
4945 gdes->vf.op = VDEF_MAXIMUM;
4946 else if (!strcmp("AVERAGE", func))
4947 gdes->vf.op = VDEF_AVERAGE;
4948 else if (!strcmp("STDEV", func))
4949 gdes->vf.op = VDEF_STDEV;
4950 else if (!strcmp("MINIMUM", func))
4951 gdes->vf.op = VDEF_MINIMUM;
4952 else if (!strcmp("TOTAL", func))
4953 gdes->vf.op = VDEF_TOTAL;
4954 else if (!strcmp("FIRST", func))
4955 gdes->vf.op = VDEF_FIRST;
4956 else if (!strcmp("LAST", func))
4957 gdes->vf.op = VDEF_LAST;
4958 else if (!strcmp("LSLSLOPE", func))
4959 gdes->vf.op = VDEF_LSLSLOPE;
4960 else if (!strcmp("LSLINT", func))
4961 gdes->vf.op = VDEF_LSLINT;
4962 else if (!strcmp("LSLCORREL", func))
4963 gdes->vf.op = VDEF_LSLCORREL;
4966 ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
4969 switch (gdes->vf.op) {
4971 case VDEF_PERCENTNAN:
4972 if (isnan(param)) { /* no parameter given */
4974 ("Function '%s' needs parameter in VDEF '%s'\n",
4978 if (param >= 0.0 && param <= 100.0) {
4979 gdes->vf.param = param;
4980 gdes->vf.val = DNAN; /* undefined */
4981 gdes->vf.when = 0; /* undefined */
4985 ("Parameter '%f' out of range in VDEF '%s'\n",
4986 param, gdes->vname);
4999 case VDEF_LSLCORREL:
5001 gdes->vf.param = DNAN;
5002 gdes->vf.val = DNAN;
5007 ("Function '%s' needs no parameter in VDEF '%s'\n",
5021 graph_desc_t *src, *dst;
5025 dst = &im->gdes[gdi];
5026 src = &im->gdes[dst->vidx];
5027 data = src->data + src->ds;
5029 steps = (src->end - src->start) / src->step;
5032 ("DEBUG: start == %lu, end == %lu, %lu steps\n",
5033 src->start, src->end, steps);
5035 switch (dst->vf.op) {
5039 if ((array = (rrd_value_t*)malloc(steps * sizeof(double))) == NULL) {
5040 rrd_set_error("malloc VDEV_PERCENT");
5043 for (step = 0; step < steps; step++) {
5044 array[step] = data[step * src->ds_cnt];
5046 qsort(array, step, sizeof(double), vdef_percent_compar);
5047 field = round((dst->vf.param * (double)(steps - 1)) / 100.0);
5048 dst->vf.val = array[field];
5049 dst->vf.when = 0; /* no time component */
5053 for (step = 0; step < steps; step++)
5054 printf("DEBUG: %3li:%10.2f %c\n",
5055 step, array[step], step == field ? '*' : ' ');
5059 case VDEF_PERCENTNAN:{
5062 /* count number of "valid" values */
5064 for (step = 0; step < steps; step++) {
5065 if (!isnan(data[step * src->ds_cnt])) { nancount++; }
5067 /* and allocate it */
5068 if ((array = (rrd_value_t*)malloc(nancount * sizeof(double))) == NULL) {
5069 rrd_set_error("malloc VDEV_PERCENT");
5072 /* and fill it in */
5074 for (step = 0; step < steps; step++) {
5075 if (!isnan(data[step * src->ds_cnt])) {
5076 array[field] = data[step * src->ds_cnt];
5080 qsort(array, nancount, sizeof(double), vdef_percent_compar);
5081 field = round( dst->vf.param * (double)(nancount - 1) / 100.0);
5082 dst->vf.val = array[field];
5083 dst->vf.when = 0; /* no time component */
5090 while (step != steps && isnan(data[step * src->ds_cnt]))
5092 if (step == steps) {
5097 dst->vf.val = data[step * src->ds_cnt];
5098 dst->vf.when = src->start + (step + 1) * src->step;
5101 while (step != steps) {
5102 if (finite(data[step * src->ds_cnt])) {
5103 if (data[step * src->ds_cnt] > dst->vf.val) {
5104 dst->vf.val = data[step * src->ds_cnt];
5105 dst->vf.when = src->start + (step + 1) * src->step;
5117 double average = 0.0;
5119 for (step = 0; step < steps; step++) {
5120 if (finite(data[step * src->ds_cnt])) {
5121 sum += data[step * src->ds_cnt];
5126 if (dst->vf.op == VDEF_TOTAL) {
5127 dst->vf.val = sum * src->step;
5128 dst->vf.when = 0; /* no time component */
5130 } else if (dst->vf.op == VDEF_AVERAGE) {
5131 dst->vf.val = sum / cnt;
5132 dst->vf.when = 0; /* no time component */
5135 average = sum / cnt;
5137 for (step = 0; step < steps; step++) {
5138 if (finite(data[step * src->ds_cnt])) {
5139 sum += pow((data[step * src->ds_cnt] - average), 2.0);
5142 dst->vf.val = pow(sum / cnt, 0.5);
5143 dst->vf.when = 0; /* no time component */
5155 while (step != steps && isnan(data[step * src->ds_cnt]))
5157 if (step == steps) {
5162 dst->vf.val = data[step * src->ds_cnt];
5163 dst->vf.when = src->start + (step + 1) * src->step;
5166 while (step != steps) {
5167 if (finite(data[step * src->ds_cnt])) {
5168 if (data[step * src->ds_cnt] < dst->vf.val) {
5169 dst->vf.val = data[step * src->ds_cnt];
5170 dst->vf.when = src->start + (step + 1) * src->step;
5178 /* The time value returned here is one step before the
5179 * actual time value. This is the start of the first
5183 while (step != steps && isnan(data[step * src->ds_cnt]))
5185 if (step == steps) { /* all entries were NaN */
5190 dst->vf.val = data[step * src->ds_cnt];
5191 dst->vf.when = src->start + step * src->step;
5196 /* The time value returned here is the
5197 * actual time value. This is the end of the last
5201 while (step >= 0 && isnan(data[step * src->ds_cnt]))
5203 if (step < 0) { /* all entries were NaN */
5208 dst->vf.val = data[step * src->ds_cnt];
5209 dst->vf.when = src->start + (step + 1) * src->step;
5215 case VDEF_LSLCORREL:{
5216 /* Bestfit line by linear least squares method */
5219 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
5226 for (step = 0; step < steps; step++) {
5227 if (finite(data[step * src->ds_cnt])) {
5230 SUMxx += step * step;
5231 SUMxy += step * data[step * src->ds_cnt];
5232 SUMy += data[step * src->ds_cnt];
5233 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
5237 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
5238 y_intercept = (SUMy - slope * SUMx) / cnt;
5241 (SUMx * SUMy) / cnt) /
5243 (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
5245 if (dst->vf.op == VDEF_LSLSLOPE) {
5246 dst->vf.val = slope;
5249 } else if (dst->vf.op == VDEF_LSLINT) {
5250 dst->vf.val = y_intercept;
5253 } else if (dst->vf.op == VDEF_LSLCORREL) {
5254 dst->vf.val = correl;
5269 /* NaN < -INF < finite_values < INF */
5270 int vdef_percent_compar(
5276 /* Equality is not returned; this doesn't hurt except
5277 * (maybe) for a little performance.
5280 /* First catch NaN values. They are smallest */
5281 if (isnan(*(double *) a))
5283 if (isnan(*(double *) b))
5285 /* NaN doesn't reach this part so INF and -INF are extremes.
5286 * The sign from isinf() is compatible with the sign we return
5288 if (isinf(*(double *) a))
5289 return isinf(*(double *) a);
5290 if (isinf(*(double *) b))
5291 return isinf(*(double *) b);
5292 /* If we reach this, both values must be finite */
5293 if (*(double *) a < *(double *) b)
5302 rrd_info_type_t type,
5303 rrd_infoval_t value)
5305 im->grinfo_current = rrd_info_push(im->grinfo_current, key, type, value);
5306 if (im->grinfo == NULL) {
5307 im->grinfo = im->grinfo_current;
5318 /* Handling based on
5319 - ANSI C99 Specifications http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf
5320 - Single UNIX Specification version 2 http://www.opengroup.org/onlinepubs/007908799/xsh/strftime.html
5321 - POSIX:2001/Single UNIX Specification version 3 http://www.opengroup.org/onlinepubs/009695399/functions/strftime.html
5322 - POSIX:2008 Specifications http://www.opengroup.org/onlinepubs/9699919799/functions/strftime.html
5323 Specifications tells
5324 "If a conversion specifier is not one of the above, the behavior is undefined."
5327 "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.
5330 "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."
5332 POSIX:2008 introduce more complexe behavior that are not handled here.
5334 According to this, this code will replace:
5335 - % followed by @ by a %@
5336 - % followed by by a %SPACE
5337 - % followed by . by a %.
5338 - % followed by % by a %
5339 - % followed by t by a TAB
5340 - % followed by E then anything by '-'
5341 - % followed by O then anything by '-'
5342 - % followed by anything else by at least one '-'. More characters may be added to better fit expected output length
5346 for(j = 0; (j < FMT_LEG_LEN - 1) && (jj < FMT_LEG_LEN); j++) { /* we don't need to parse the last char */
5347 if (format[j] == '%') {
5348 if ((format[j+1] == 'E') || (format[j+1] == 'O')) {
5350 j+=2; /* We skip next 2 following char */
5351 } else if ((format[j+1] == 'C') || (format[j+1] == 'd') ||
5352 (format[j+1] == 'g') || (format[j+1] == 'H') ||
5353 (format[j+1] == 'I') || (format[j+1] == 'm') ||
5354 (format[j+1] == 'M') || (format[j+1] == 'S') ||
5355 (format[j+1] == 'U') || (format[j+1] == 'V') ||
5356 (format[j+1] == 'W') || (format[j+1] == 'y')) {
5358 if (jj < FMT_LEG_LEN) {
5361 j++; /* We skip the following char */
5362 } else if (format[j+1] == 'j') {
5364 if (jj < FMT_LEG_LEN - 1) {
5368 j++; /* We skip the following char */
5369 } else if ((format[j+1] == 'G') || (format[j+1] == 'Y')) {
5370 /* Assuming Year on 4 digit */
5372 if (jj < FMT_LEG_LEN - 2) {
5377 j++; /* We skip the following char */
5378 } else if (format[j+1] == 'R') {
5380 if (jj < FMT_LEG_LEN - 3) {
5386 j++; /* We skip the following char */
5387 } else if (format[j+1] == 'T') {
5389 if (jj < FMT_LEG_LEN - 6) {
5398 j++; /* We skip the following char */
5399 } else if (format[j+1] == 'F') {
5401 if (jj < FMT_LEG_LEN - 8) {
5412 j++; /* We skip the following char */
5413 } else if (format[j+1] == 'D') {
5415 if (jj < FMT_LEG_LEN - 6) {
5424 j++; /* We skip the following char */
5425 } else if (format[j+1] == 'n') {
5426 result[jj++] = '\r';
5427 result[jj++] = '\n';
5428 j++; /* We skip the following char */
5429 } else if (format[j+1] == 't') {
5430 result[jj++] = '\t';
5431 j++; /* We skip the following char */
5432 } else if (format[j+1] == '%') {
5434 j++; /* We skip the following char */
5435 } else if (format[j+1] == ' ') {
5436 if (jj < FMT_LEG_LEN - 1) {
5440 j++; /* We skip the following char */
5441 } else if (format[j+1] == '.') {
5442 if (jj < FMT_LEG_LEN - 1) {
5446 j++; /* We skip the following char */
5447 } else if (format[j+1] == '@') {
5448 if (jj < FMT_LEG_LEN - 1) {
5452 j++; /* We skip the following char */
5455 j++; /* We skip the following char */
5458 result[jj++] = format[j];
5461 result[jj] = '\0'; /* We must force the end of the string */