[rrd-developers] STDDEV as an additional CF
Susumu Shimizu
shimizu at ntt-20.ecl.net
Tue Mar 2 11:32:52 MET 2004
Hello list,
I thought it would be useful if standard deviation is calculated by a
CF. Using this function, one can easily trace average +/- 2sigma
values, to find some anomalies and/or to recognize normal deviation
of the data set.
Here's a sample usage. Patches by my quick hack (maybe ugly) are
also attached.
/usr/local/rrdtool-1.1.0/bin/rrdtool graph sample.png \
--font DEFAULT:8:/usr/X11R6/lib/X11/fonts/TrueType2/cour.ttf \
--title="CORE POS Dayly" --step 300 -x MINUTE:30:HOUR:1:HOUR:2:3600:%R \
-w 540 -h 120 \
DEF:val3=core-pos.rrd:input:AVERAGE \
DEF:val6=core-pos.rrd:output:AVERAGE \
DEF:val7=core-pos.rrd:input:STDDEV \
DEF:val8=core-pos.rrd:output:STDDEV \
CDEF:iplus=val3,val7,2,*,+ \
CDEF:iminus=val3,val7,2,*,-,0,MAX \
CDEF:oplus=val6,val8,2,*,+ \
CDEF:ominus=val6,val8,2,*,-,0,MAX \
AREA:iminus STACK:iplus#00EE00:IN_2_sigma \
LINE2:val3#FFF000:IN_AVE \
LINE1:oplus#0000FF:OUT_2_sigma LINE1:ominus#0000FF:OUT_2_sigma \
LINE2:val6#CC00CC:OUT_AVE
This generates this kind of graph; http://rodem.ingrid.org:8080/sample.png
Sorry if this topic was already discussed on the list...I just thought
why this feature does not exist. Maybe because it is for niche
market, or because Holt-Winters is powerful enough?
Thanks a lot,
Susumu
------------------------------------------------------------
diff --recursive --unified rrdtool-2004-03-02.orig/bindings/perl-shared/Makefile.PL rrdtool-2004-03-02/bindings/perl-shared/Makefile.PL
--- rrdtool-2004-03-02.orig/bindings/perl-shared/Makefile.PL Tue Jan 20 08:17:00 2004
+++ rrdtool-2004-03-02/bindings/perl-shared/Makefile.PL Wed Feb 25 15:16:31 2004
@@ -13,7 +13,7 @@
'INC' => '-I../../src -I../../libraries/gd1.3',
# where to look for the necessary libraries
# Perl will figure out which one is valid
- 'depend' => {'RRDs.c' => "../../src/.libs/librrd_private.a"},
+ 'depend' => {'RRDs.c' => "../../src/.libs/librrd.a"},
'dynamic_lib' => {'OTHERLDFLAGS' => "$librrd -lm"},
'realclean' => {FILES => 't/demo?.rrd t/demo?.png' }
);
diff --recursive --unified rrdtool-2004-03-02.orig/src/rrd_format.c rrdtool-2004-03-02/src/rrd_format.c
--- rrdtool-2004-03-02.orig/src/rrd_format.c Thu Feb 13 16:05:27 2003
+++ rrdtool-2004-03-02/src/rrd_format.c Thu Feb 26 17:45:38 2004
@@ -74,6 +74,7 @@
converter(SEASONAL,CF_SEASONAL)
converter(DEVSEASONAL,CF_DEVSEASONAL)
converter(FAILURES,CF_FAILURES)
+ converter(STDDEV,CF_STDDEV)
rrd_set_error("unknown consolidation function '%s'",string);
return(-1);
}
diff --recursive --unified rrdtool-2004-03-02.orig/src/rrd_format.h rrdtool-2004-03-02/src/rrd_format.h
--- rrdtool-2004-03-02.orig/src/rrd_format.h Tue Apr 1 06:22:12 2003
+++ rrdtool-2004-03-02/src/rrd_format.h Thu Feb 26 17:50:46 2004
@@ -177,10 +177,12 @@
/* An array of smoothed seasonal deviations. Requires
* an RRA of type CF_HWPREDICT for this data source.
* */
- CF_FAILURES};
+ CF_FAILURES,
/* A binary array of failure indicators: 1 indicates
* that the number of violations in the prescribed
* window exceeded the prescribed threshold. */
+ CF_STDDEV};/* for standard deviation */
+
#define MAX_RRA_PAR_EN 10
enum rra_par_en { RRA_cdp_xff_val=0, /* what part of the consolidated
@@ -263,7 +265,7 @@
enum pdp_par_en { PDP_unkn_sec_cnt=0, /* how many seconds of the current
* pdp value is unknown data? */
- PDP_val}; /* current value of the pdp.
+ PDP_val, PDP_val2}; /* current value of the pdp.
this depends on dst */
typedef struct pdp_prep_t{
@@ -333,7 +335,8 @@
/* Last iteration seasonal coeffient. */
CDP_seasonal_deviation = CDP_hw_intercept,
CDP_last_seasonal_deviation = CDP_hw_last_intercept,
- CDP_init_seasonal = CDP_null_count};
+ CDP_init_seasonal = CDP_null_count,
+ CDP_val2};
/* init_seasonal is a flag which when > 0, forces smoothing updates
* to occur when rra_ptr.cur_row == 0 */
diff --recursive --unified rrdtool-2004-03-02.orig/src/rrd_graph.c rrdtool-2004-03-02/src/rrd_graph.c
--- rrdtool-2004-03-02.orig/src/rrd_graph.c Sat Dec 27 01:54:55 2003
+++ rrdtool-2004-03-02/src/rrd_graph.c Mon Mar 1 19:03:41 2004
@@ -604,6 +604,7 @@
for (dst_row=0;row_cnt>=reduce_factor;dst_row++) {
for (col=0;col<(*ds_cnt);col++) {
rrd_value_t newval=DNAN;
+ rrd_value_t newval2=DNAN;
unsigned long validval=0;
for (i=0;i<reduce_factor;i++) {
@@ -611,7 +612,10 @@
continue;
}
validval++;
- if (isnan(newval)) newval = srcptr[i*(*ds_cnt)+col];
+ if (isnan(newval)) {
+ newval = srcptr[i*(*ds_cnt)+col];
+ newval2 = srcptr[i*(*ds_cnt)+col] * srcptr[i*(*ds_cnt)+col];
+ }
else {
switch (cf) {
case CF_HWPREDICT:
@@ -632,13 +636,16 @@
case CF_LAST:
newval = srcptr[i*(*ds_cnt)+col];
break;
+ case CF_STDDEV:
+ newval += srcptr[i*(*ds_cnt)+col];
+ newval2 += srcptr[i*(*ds_cnt)+col] * srcptr[i*(*ds_cnt)+col];
+ break;
}
}
- }
- if (validval == 0){newval = DNAN;} else{
- switch (cf) {
+ if (validval == 0){newval = DNAN;} else{
+ switch (cf) {
case CF_HWPREDICT:
- case CF_DEVSEASONAL:
+ case CF_DEVSEASONAL:
case CF_DEVPREDICT:
case CF_SEASONAL:
case CF_AVERAGE:
@@ -649,32 +656,38 @@
case CF_MAXIMUM:
case CF_LAST:
break;
+ case CF_STDDEV:
+ newval = (validval > 1)?
+ sqrt((newval2 - newval * newval / validval) / (validval - 1)):
+ DNAN;
+ break;
+ }
}
+ *dstptr++=newval;
}
- *dstptr++=newval;
+ srcptr+=(*ds_cnt)*reduce_factor;
+ row_cnt-=reduce_factor;
}
- srcptr+=(*ds_cnt)*reduce_factor;
- row_cnt-=reduce_factor;
- }
/* If we had to alter the endtime, we didn't have enough
** source rows to fill the last row. Fill it with NaN.
*/
- if (end_offset) for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
+
+ if (end_offset) for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
#ifdef DEBUG_REDUCE
- row_cnt = ((*end)-(*start))/ *step;
- srcptr = *data;
- printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
+ row_cnt = ((*end)-(*start))/ *step;
+ srcptr = *data;
+ printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
row_cnt,*start,*end,*step);
-for (col=0;col<row_cnt;col++) {
- printf("time %10lu: ",*start+(col+1)*(*step));
- for (i=0;i<*ds_cnt;i++)
- printf(" %8.2e",srcptr[*ds_cnt*col+i]);
- printf("\n");
-}
+ for (col=0;col<row_cnt;col++) {
+ printf("time %10lu: ",*start+(col+1)*(*step));
+ for (i=0;i<*ds_cnt;i++)
+ printf(" %8.2e",srcptr[*ds_cnt*col+i]);
+ printf("\n");
+ }
#endif
+ }
}
-
/* get the data required for the graphs from the
relevant rrds ... */
@@ -1189,6 +1202,7 @@
{
long i,ii,validsteps;
double printval;
+ double printval2;
time_t printtime;
int graphelement = 0;
long vidx;
@@ -1221,6 +1235,7 @@
/ im->gdes[vidx].step
* im->gdes[vidx].ds_cnt);
printval = DNAN;
+ printval2 = DNAN;
validsteps = 0;
for( ii=im->gdes[vidx].ds;
ii < max_ii;
@@ -1229,6 +1244,7 @@
continue;
if (isnan(printval)){
printval = im->gdes[vidx].data[ii];
+ printval2 = printval * printval;
validsteps++;
continue;
}
@@ -1251,12 +1267,21 @@
break;
case CF_LAST:
printval = im->gdes[vidx].data[ii];
+ case CF_STDDEV:
+ validsteps++;
+ printval += im->gdes[vidx].data[ii];
+ printval2 += im->gdes[vidx].data[ii] * im->gdes[vidx].data[ii];
+ break;
}
}
if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
if (validsteps > 1) {
printval = (printval / validsteps);
}
+ } else if(im->gdes[i].cf == CF_STDDEV) {
+ printval = (validsteps > 1)?
+ sqrt((printval2 - printval * printval / validsteps) / (validsteps - 1)):
+ DNAN;
}
} /* prepare printval */
diff --recursive --unified rrdtool-2004-03-02.orig/src/rrd_update.c rrdtool-2004-03-02/src/rrd_update.c
--- rrdtool-2004-03-02.orig/src/rrd_update.c Wed Nov 12 04:46:21 2003
+++ rrdtool-2004-03-02/src/rrd_update.c Mon Mar 1 19:08:22 2004
@@ -738,8 +738,10 @@
for(i=0;i<rrd.stat_head->ds_cnt;i++){
if(isnan(pdp_new[i]))
rrd.pdp_prep[i].scratch[PDP_unkn_sec_cnt].u_cnt += interval;
- else
+ else {
rrd.pdp_prep[i].scratch[PDP_val].u_val+= pdp_new[i];
+ rrd.pdp_prep[i].scratch[PDP_val2].u_val+= pdp_new[i] * pdp_new[i];
+ }
#ifdef DEBUG
fprintf(stderr,
"NO PDP ds[%lu]\t"
@@ -762,9 +764,12 @@
/* update pdp_prep to the current pdp_st */
if(isnan(pdp_new[i]))
rrd.pdp_prep[i].scratch[PDP_unkn_sec_cnt].u_cnt += pre_int;
- else
+ else {
rrd.pdp_prep[i].scratch[PDP_val].u_val +=
pdp_new[i]/(double)interval*(double)pre_int;
+ rrd.pdp_prep[i].scratch[PDP_val2].u_val +=
+ ((pdp_new[i]/(double)interval*(double)pre_int) * (pdp_new[i]/(double)interval*(double)pre_int));
+ }
/* if too much of the pdp_prep is unknown we dump it */
if ((rrd.pdp_prep[i].scratch[PDP_unkn_sec_cnt].u_cnt
@@ -803,10 +808,13 @@
if(isnan(pdp_new[i])){
rrd.pdp_prep[i].scratch[PDP_unkn_sec_cnt].u_cnt = post_int;
rrd.pdp_prep[i].scratch[PDP_val].u_val = 0.0;
+ rrd.pdp_prep[i].scratch[PDP_val2].u_val = 0.0;
} else {
rrd.pdp_prep[i].scratch[PDP_unkn_sec_cnt].u_cnt = 0;
rrd.pdp_prep[i].scratch[PDP_val].u_val =
pdp_new[i]/(double)interval*(double)post_int;
+ rrd.pdp_prep[i].scratch[PDP_val2].u_val =
+ (pdp_new[i]/(double)interval*(double)post_int) * (pdp_new[i]/(double)interval*(double)post_int);
}
#ifdef DEBUG
@@ -975,19 +983,30 @@
rrd.cdp_prep[iii].scratch[CDP_val].u_val = pdp_temp[ii] *
((elapsed_pdp_st - start_pdp_offset) % rrd.rra_def[i].pdp_cnt);
}
- } else {
+ } else if (current_cf == CF_STDDEV) {
+ if (isnan(pdp_temp[ii])) {
+ rrd.cdp_prep[iii].scratch[CDP_val].u_val = DNAN;
+ rrd.cdp_prep[iii].scratch[CDP_val2].u_val = DNAN;
+ } else {
+ rrd.cdp_prep[iii].scratch[CDP_val].u_val = pdp_temp[ii] *
+ ((elapsed_pdp_st - start_pdp_offset) % rrd.rra_def[i].pdp_cnt);
+ rrd.cdp_prep[iii].scratch[CDP_val2].u_val = (pdp_temp[ii] *
+ ((elapsed_pdp_st - start_pdp_offset) % rrd.rra_def[i].pdp_cnt)) * (pdp_temp[ii] *
+ ((elapsed_pdp_st - start_pdp_offset) % rrd.rra_def[i].pdp_cnt));
+ }
+ } else {
rrd.cdp_prep[iii].scratch[CDP_val].u_val = pdp_temp[ii];
}
} else {
- rrd_value_t cum_val, cur_val;
+ rrd_value_t cum_val, cur_val, cum_val2;
switch (current_cf) {
case CF_AVERAGE:
cum_val = IFDNAN(rrd.cdp_prep[iii].scratch[CDP_val].u_val, 0.0);
cur_val = IFDNAN(pdp_temp[ii],0.0);
- rrd.cdp_prep[iii].scratch[CDP_primary_val].u_val =
- (cum_val + cur_val * start_pdp_offset) /
- (rrd.rra_def[i].pdp_cnt
- -rrd.cdp_prep[iii].scratch[CDP_unkn_pdp_cnt].u_cnt);
+
+ rrd.cdp_prep[iii].scratch[CDP_primary_val].u_val =
+ (cum_val + cur_val * start_pdp_offset) /
+ (rrd.rra_def[i].pdp_cnt - rrd.cdp_prep[iii].scratch[CDP_unkn_pdp_cnt].u_cnt);
/* initialize carry over value */
if (isnan(pdp_temp[ii])) {
rrd.cdp_prep[iii].scratch[CDP_val].u_val = DNAN;
@@ -996,7 +1015,35 @@
((elapsed_pdp_st - start_pdp_offset) % rrd.rra_def[i].pdp_cnt);
}
break;
- case CF_MAXIMUM:
+ case CF_STDDEV:
+ cum_val = IFDNAN(rrd.cdp_prep[iii].scratch[CDP_val].u_val, 0.0);
+ cur_val = IFDNAN(pdp_temp[ii],0.0);
+ cum_val2 = IFDNAN(rrd.cdp_prep[iii].scratch[CDP_val2].u_val, 0.0);
+
+ if ((rrd.rra_def[i].pdp_cnt - rrd.cdp_prep[iii].scratch[CDP_unkn_pdp_cnt].u_cnt) == 1)
+ {
+ rrd.cdp_prep[iii].scratch[CDP_primary_val].u_val = DNAN;
+ }
+ else {
+ rrd.cdp_prep[iii].scratch[CDP_primary_val].u_val = sqrt(
+ ((cum_val2 + cur_val * cur_val * start_pdp_offset * start_pdp_offset) -
+ (cum_val + cur_val * start_pdp_offset) * (cum_val + cur_val * start_pdp_offset)/
+ (rrd.rra_def[i].pdp_cnt - rrd.cdp_prep[iii].scratch[CDP_unkn_pdp_cnt].u_cnt)) /
+ ((rrd.rra_def[i].pdp_cnt - rrd.cdp_prep[iii].scratch[CDP_unkn_pdp_cnt].u_cnt)-1)) ;
+ }
+
+ /* initialize carry over value */
+ if (isnan(pdp_temp[ii])) {
+ rrd.cdp_prep[iii].scratch[CDP_val].u_val = DNAN;
+ rrd.cdp_prep[iii].scratch[CDP_val2].u_val = DNAN;
+ } else {
+ rrd.cdp_prep[iii].scratch[CDP_val].u_val = pdp_temp[ii] *
+ ((elapsed_pdp_st - start_pdp_offset) % rrd.rra_def[i].pdp_cnt);
+ rrd.cdp_prep[iii].scratch[CDP_val2].u_val =
+ rrd.cdp_prep[iii].scratch[CDP_val].u_val * rrd.cdp_prep[iii].scratch[CDP_val].u_val;
+ }
+ break;
+ case CF_MAXIMUM:
cum_val = IFDNAN(rrd.cdp_prep[iii].scratch[CDP_val].u_val, -DINF);
cur_val = IFDNAN(pdp_temp[ii],-DINF);
#ifdef DEBUG
@@ -1034,6 +1081,7 @@
/* initialize carry over value */
rrd.cdp_prep[iii].scratch[CDP_val].u_val = pdp_temp[ii];
break;
+
case CF_LAST:
default:
rrd.cdp_prep[iii].scratch[CDP_primary_val].u_val = pdp_temp[ii];
@@ -1061,10 +1109,10 @@
}
#endif
if (isnan(pdp_temp[ii])) {
- rrd.cdp_prep[iii].scratch[CDP_unkn_pdp_cnt].u_cnt += elapsed_pdp_st;
+ rrd.cdp_prep[iii].scratch[CDP_unkn_pdp_cnt].u_cnt += elapsed_pdp_st;
} else if (isnan(rrd.cdp_prep[iii].scratch[CDP_val].u_val))
- {
- if (current_cf == CF_AVERAGE) {
+ {
+ if ((current_cf == CF_AVERAGE)||(current_cf == CF_STDDEV)) {
rrd.cdp_prep[iii].scratch[CDP_val].u_val = pdp_temp[ii] *
elapsed_pdp_st;
} else {
@@ -1074,12 +1122,29 @@
fprintf(stderr,"Initialize CDP_val for RRA %lu DS %lu: %10.2f\n",
i,ii,rrd.cdp_prep[iii].scratch[CDP_val].u_val);
#endif
- } else {
- switch (current_cf) {
+ } else if (isnan(rrd.cdp_prep[iii].scratch[CDP_val2].u_val))
+ {
+ if (current_cf == CF_STDDEV) {
+ rrd.cdp_prep[iii].scratch[CDP_val2].u_val = pdp_temp[ii] * elapsed_pdp_st * pdp_temp[ii] * elapsed_pdp_st;
+ } else {
+ rrd.cdp_prep[iii].scratch[CDP_val2].u_val = pdp_temp[ii];
+ }
+#ifdef DEBUG
+ fprintf(stderr,"Initialize CDP_val for RRA %lu DS %lu: %10.2f\n",
+ i,ii,rrd.cdp_prep[iii].scratch[CDP_val].u_val);
+#endif
+ } else {
+ switch (current_cf) {
case CF_AVERAGE:
rrd.cdp_prep[iii].scratch[CDP_val].u_val += pdp_temp[ii] *
elapsed_pdp_st;
break;
+ case CF_STDDEV:
+ rrd.cdp_prep[iii].scratch[CDP_val].u_val += pdp_temp[ii] *
+ elapsed_pdp_st;
+ rrd.cdp_prep[iii].scratch[CDP_val2].u_val += (pdp_temp[ii] * elapsed_pdp_st) *
+ (pdp_temp[ii] * elapsed_pdp_st);
+ break;
case CF_MINIMUM:
if (pdp_temp[ii] < rrd.cdp_prep[iii].scratch[CDP_val].u_val)
rrd.cdp_prep[iii].scratch[CDP_val].u_val = pdp_temp[ii];
@@ -1092,19 +1157,20 @@
default:
rrd.cdp_prep[iii].scratch[CDP_val].u_val = pdp_temp[ii];
break;
- }
- }
+ }
+ }
}
} else { /* rrd.rra_def[i].pdp_cnt == 1 */
if (elapsed_pdp_st > 2)
{
switch (current_cf) {
case CF_AVERAGE:
+ case CF_STDDEV:
default:
- rrd.cdp_prep[iii].scratch[CDP_primary_val].u_val=pdp_temp[ii];
- rrd.cdp_prep[iii].scratch[CDP_secondary_val].u_val=pdp_temp[ii];
- break;
- case CF_SEASONAL:
+ rrd.cdp_prep[iii].scratch[CDP_primary_val].u_val=pdp_temp[ii];
+ rrd.cdp_prep[iii].scratch[CDP_secondary_val].u_val=pdp_temp[ii];
+ break;
+ case CF_SEASONAL:
case CF_DEVSEASONAL:
/* need to update cached seasonal values, so they are consistent
* with the bulk update */
@@ -1115,7 +1181,7 @@
rrd.cdp_prep[iii].scratch[CDP_hw_seasonal].u_val =
seasonal_coef[ii];
break;
- case CF_HWPREDICT:
+ case CF_HWPREDICT:
/* need to update the null_count and last_null_count.
* even do this for non-DNAN pdp_temp because the
* algorithm is not learning from batch updates. */
@@ -1125,18 +1191,18 @@
elapsed_pdp_st - 1;
/* fall through */
case CF_DEVPREDICT:
- rrd.cdp_prep[iii].scratch[CDP_primary_val].u_val = DNAN;
- rrd.cdp_prep[iii].scratch[CDP_secondary_val].u_val = DNAN;
- break;
- case CF_FAILURES:
+ rrd.cdp_prep[iii].scratch[CDP_primary_val].u_val = DNAN;
+ rrd.cdp_prep[iii].scratch[CDP_secondary_val].u_val = DNAN;
+ break;
+ case CF_FAILURES:
/* do not count missed bulk values as failures */
- rrd.cdp_prep[iii].scratch[CDP_primary_val].u_val = 0;
- rrd.cdp_prep[iii].scratch[CDP_secondary_val].u_val = 0;
+ rrd.cdp_prep[iii].scratch[CDP_primary_val].u_val = 0;
+ rrd.cdp_prep[iii].scratch[CDP_secondary_val].u_val = 0;
/* need to reset violations buffer.
* could do this more carefully, but for now, just
* assume a bulk update wipes away all violations. */
- erase_violations(&rrd, iii, i);
- break;
+ erase_violations(&rrd, iii, i);
+ break;
}
}
} /* endif rrd.rra_def[i].pdp_cnt == 1 */
@@ -1146,18 +1212,18 @@
} /* endif data sources loop */
} /* end RRA Loop */
- /* this loop is only entered if elapsed_pdp_st < 3 */
- for (j = elapsed_pdp_st, scratch_idx = CDP_primary_val;
- j > 0 && j < 3; j--, scratch_idx = CDP_secondary_val)
- {
- for(i = 0, rra_start = rra_begin;
+ /* this loop is only entered if elapsed_pdp_st < 3 */
+ for (j = elapsed_pdp_st, scratch_idx = CDP_primary_val;
+ j > 0 && j < 3; j--, scratch_idx = CDP_secondary_val)
+ {
+ for(i = 0, rra_start = rra_begin;
i < rrd.stat_head->rra_cnt;
- rra_start += rrd.rra_def[i].row_cnt * rrd.stat_head -> ds_cnt * sizeof(rrd_value_t),
- i++)
+ rra_start += rrd.rra_def[i].row_cnt * rrd.stat_head -> ds_cnt * sizeof(rrd_value_t),
+ i++)
{
- if (rrd.rra_def[i].pdp_cnt > 1) continue;
+ if (rrd.rra_def[i].pdp_cnt > 1) continue;
- current_cf = cf_conv(rrd.rra_def[i].cf_nam);
+ current_cf = cf_conv(rrd.rra_def[i].cf_nam);
if (current_cf == CF_SEASONAL || current_cf == CF_DEVSEASONAL)
{
lookup_seasonal(&rrd,i,rra_start,rrd_file,
----------------------------------------------------------------------
--
Susumu Shimizu # +-----+
NTT Network innovations Labs. ####@NTT,*|
shimizu at ntt-20.ecl.net +-----+
--
Unsubscribe mailto:rrd-developers-request at list.ee.ethz.ch?subject=unsubscribe
Help mailto:rrd-developers-request at list.ee.ethz.ch?subject=help
Archive http://www.ee.ethz.ch/~slist/rrd-developers
WebAdmin http://www.ee.ethz.ch/~slist/lsg2.cgi
More information about the rrd-developers
mailing list