int format_values(strbuf_t *buf, metric_t const *m, bool store_rates) {
strbuf_printf(buf, "%.3f", CDTIME_T_TO_DOUBLE(m->time));
- if (m->family->type == METRIC_TYPE_GAUGE) {
- /* Solaris' printf tends to print NAN as "-nan", breaking unit tests, so we
- * introduce a special case here. */
- if (isnan(m->value.gauge)) {
- strbuf_print(buf, ":nan");
- } else {
- strbuf_printf(buf, ":" GAUGE_FORMAT, m->value.gauge);
- }
- } else if (store_rates) {
+ if (store_rates) {
gauge_t rates = NAN;
- int status = uc_get_rate(m, &rates);
- if (status != 0) {
- WARNING("format_values: uc_get_rate failed.");
- return status;
+ int err = uc_get_rate(m, &rates);
+ if (err) {
+ ERROR("format_values: uc_get_rate failed: %s", STRERROR(err));
+ return err;
}
if (isnan(rates)) {
strbuf_print(buf, ":nan");
} else {
strbuf_printf(buf, ":" GAUGE_FORMAT, rates);
}
- } else if (m->family->type == METRIC_TYPE_COUNTER) {
- strbuf_printf(buf, ":%" PRIu64, m->value.counter);
- } else if (m->family->type == METRIC_TYPE_FPCOUNTER) {
- strbuf_printf(buf, ":" GAUGE_FORMAT, m->value.fpcounter);
- } else if (m->family->type == DS_TYPE_DERIVE) {
- strbuf_printf(buf, ":%" PRIi64, m->value.derive);
- } else {
- ERROR("format_values: Unknown metric type: %d", m->family->type);
- return -1;
+ return 0;
}
- return 0;
+ if (m->family->type == DS_TYPE_DERIVE) {
+ return strbuf_printf(buf, ":%" PRIi64, m->value.derive);
+ }
+
+ switch (m->family->type) {
+ case METRIC_TYPE_GAUGE:
+ /* Solaris' printf tends to print NAN as "-nan", breaking unit tests, so we
+ * introduce a special case here. */
+ if (isnan(m->value.gauge)) {
+ return strbuf_print(buf, ":nan");
+ }
+ return strbuf_printf(buf, ":" GAUGE_FORMAT, m->value.gauge);
+ case METRIC_TYPE_COUNTER:
+ return strbuf_printf(buf, ":%" PRIu64, m->value.counter);
+ case METRIC_TYPE_FPCOUNTER:
+ return strbuf_printf(buf, ":" GAUGE_FORMAT, m->value.fpcounter);
+ case METRIC_TYPE_UNTYPED:
+ break;
+ }
+ ERROR("format_values: Unknown metric type: %d", m->family->type);
+ return EINVAL;
} /* }}} int format_values */
int parse_value(const char *value_orig, value_t *ret_value, int ds_type) {
} /* counter_t counter_diff */
int rate_to_value(value_t *ret_value, gauge_t rate, /* {{{ */
- rate_to_value_state_t *state, int ds_type, cdtime_t t) {
+ rate_to_value_state_t *state, metric_type_t type,
+ cdtime_t t) {
gauge_t delta_gauge;
cdtime_t delta_t;
- if (ds_type == DS_TYPE_GAUGE) {
+ if (type == METRIC_TYPE_GAUGE) {
state->last_value.gauge = rate;
state->last_time = t;
/* Counter can't handle negative rates. Reset "last time" to zero, so that
* the next valid rate will re-initialize the structure. */
- if ((rate < 0.0) && (ds_type == DS_TYPE_COUNTER)) {
+ if ((rate < 0.0) &&
+ (type == METRIC_TYPE_COUNTER || type == METRIC_TYPE_FPCOUNTER)) {
memset(state, 0, sizeof(*state));
return EINVAL;
}
/* Previous value is invalid. */
if (state->last_time == 0) /* {{{ */
{
- if (ds_type == DS_TYPE_DERIVE) {
- state->last_value.derive = (derive_t)rate;
+ if (type == DS_TYPE_DERIVE) {
+ state->last_value.derive = (derive_t)floor(rate);
state->residual = rate - ((gauge_t)state->last_value.derive);
- } else if (ds_type == DS_TYPE_COUNTER) {
+ state->last_time = t;
+ return EAGAIN;
+ }
+
+ switch (type) {
+ case METRIC_TYPE_GAUGE:
+ /* not reached */
+ break;
+ case METRIC_TYPE_COUNTER:
state->last_value.counter = (counter_t)rate;
state->residual = rate - ((gauge_t)state->last_value.counter);
- } else {
- assert(23 == 42);
+ break;
+ case METRIC_TYPE_FPCOUNTER:
+ state->last_value.fpcounter = (fpcounter_t)rate;
+ state->residual = 0;
+ break;
+ case METRIC_TYPE_UNTYPED:
+ ERROR("rate_to_value: invalid metric type: %d", type);
+ return EINVAL;
}
state->last_time = t;
return EAGAIN;
} /* }}} */
- if (ds_type == DS_TYPE_DERIVE) {
- derive_t delta_derive = (derive_t)delta_gauge;
-
+ if (type == DS_TYPE_DERIVE) {
+ derive_t delta_derive = (derive_t)floor(delta_gauge);
state->last_value.derive += delta_derive;
state->residual = delta_gauge - ((gauge_t)delta_derive);
- } else if (ds_type == DS_TYPE_COUNTER) {
- counter_t delta_counter = (counter_t)delta_gauge;
+ state->last_time = t;
+ *ret_value = state->last_value;
+ return 0;
+ }
+
+ switch (type) {
+ case METRIC_TYPE_GAUGE:
+ /* not reached */
+ break;
+ case METRIC_TYPE_COUNTER: {
+ counter_t delta_counter = (counter_t)delta_gauge;
state->last_value.counter += delta_counter;
state->residual = delta_gauge - ((gauge_t)delta_counter);
- } else {
- assert(23 == 42);
+ break;
+ }
+ case METRIC_TYPE_FPCOUNTER: {
+ fpcounter_t delta_counter = (fpcounter_t)delta_gauge;
+ state->last_value.fpcounter += delta_counter;
+ state->residual = 0;
+ break;
+ }
+ case METRIC_TYPE_UNTYPED:
+ ERROR("rate_to_value: invalid metric type: %d", type);
+ return EINVAL;
}
state->last_time = t;
return 0;
} /* }}} value_t rate_to_value */
+static int calculate_rate(gauge_t *ret_rate, value_t value, metric_type_t type,
+ gauge_t interval, value_to_rate_state_t *state) {
+ if (type == DS_TYPE_DERIVE) {
+ derive_t diff = value.derive - state->last_value.derive;
+ *ret_rate = ((gauge_t)diff) / ((gauge_t)interval);
+ return 0;
+ }
+
+ switch (type) {
+ case METRIC_TYPE_GAUGE: {
+ *ret_rate = value.gauge;
+ return 0;
+ }
+ case METRIC_TYPE_COUNTER: {
+ counter_t diff = counter_diff(state->last_value.counter, value.counter);
+ *ret_rate = ((gauge_t)diff) / ((gauge_t)interval);
+ return 0;
+ }
+ case METRIC_TYPE_FPCOUNTER: {
+ if (state->last_value.fpcounter > value.fpcounter) {
+ *ret_rate = NAN;
+ return EAGAIN;
+ }
+ fpcounter_t diff = value.fpcounter - state->last_value.fpcounter;
+ *ret_rate = ((gauge_t)diff) / ((gauge_t)interval);
+ return 0;
+ }
+ case METRIC_TYPE_UNTYPED:
+ break;
+ }
+ ERROR("value_to_rate: invalid metric type: %d", type);
+ return EINVAL;
+}
+
int value_to_rate(gauge_t *ret_rate, /* {{{ */
- value_t value, int ds_type, cdtime_t t,
+ value_t value, metric_type_t type, cdtime_t t,
value_to_rate_state_t *state) {
- gauge_t interval;
-
/* Another invalid state: The time is not increasing. */
if (t <= state->last_time) {
memset(state, 0, sizeof(*state));
return EINVAL;
}
- interval = CDTIME_T_TO_DOUBLE(t - state->last_time);
-
/* Previous value is invalid. */
if (state->last_time == 0) {
state->last_value = value;
return EAGAIN;
}
- switch (ds_type) {
- case DS_TYPE_DERIVE: {
- derive_t diff = value.derive - state->last_value.derive;
- *ret_rate = ((gauge_t)diff) / ((gauge_t)interval);
- break;
- }
- case DS_TYPE_GAUGE: {
- *ret_rate = value.gauge;
- break;
- }
- case DS_TYPE_COUNTER: {
- counter_t diff = counter_diff(state->last_value.counter, value.counter);
- *ret_rate = ((gauge_t)diff) / ((gauge_t)interval);
- break;
- }
- default:
- return EINVAL;
+ gauge_t interval = CDTIME_T_TO_DOUBLE(t - state->last_time);
+ int err = calculate_rate(ret_rate, value, type, interval, state);
+ if (err && err != EAGAIN) {
+ return err;
}
state->last_value = value;
state->last_time = t;
- return 0;
+ return err;
} /* }}} value_t rate_to_value */
int service_name_to_port_number(const char *service_name) {
return 0;
}
+DEF_TEST(rate_to_value) {
+ struct {
+ char *name;
+ gauge_t rate;
+ rate_to_value_state_t state;
+ metric_type_t type;
+ cdtime_t time;
+ value_t want;
+ gauge_t want_residual;
+ int want_err;
+ } cases[] = {
+ {
+ .name = "zero value",
+ .rate = 1.,
+ .state = {.last_time = 0},
+ .type = METRIC_TYPE_COUNTER,
+ .time = TIME_T_TO_CDTIME_T(10),
+ .want_err = EAGAIN,
+ },
+ {
+ .name = "counter",
+ .rate = 1.,
+ .state =
+ {
+ .last_value = {.counter = 1000},
+ .last_time = TIME_T_TO_CDTIME_T(10),
+ .residual = 0,
+ },
+ .type = METRIC_TYPE_COUNTER,
+ .time = TIME_T_TO_CDTIME_T(20),
+ .want = {.counter = 1010},
+ },
+ {
+ .name = "residual gets rounded down",
+ .rate = 0.999,
+ .state =
+ {
+ .last_value = {.counter = 1000},
+ .last_time = TIME_T_TO_CDTIME_T(10),
+ .residual = 0,
+ },
+ .type = METRIC_TYPE_COUNTER,
+ .time = TIME_T_TO_CDTIME_T(20),
+ .want = {.counter = 1009},
+ .want_residual = 0.99,
+ },
+ {
+ .name = "residual gets added to result",
+ .rate = 0.0011,
+ .state =
+ {
+ .last_value = {.counter = 1000},
+ .last_time = TIME_T_TO_CDTIME_T(10),
+ .residual = 0.99,
+ },
+ .type = METRIC_TYPE_COUNTER,
+ .time = TIME_T_TO_CDTIME_T(20),
+ .want = {.counter = 1001},
+ .want_residual = 0.001,
+ },
+ {
+ .name = "fpcounter",
+ .rate = 1.234,
+ .state =
+ {
+ .last_value = {.fpcounter = 1000},
+ .last_time = TIME_T_TO_CDTIME_T(10),
+ .residual = 0,
+ },
+ .type = METRIC_TYPE_FPCOUNTER,
+ .time = TIME_T_TO_CDTIME_T(20),
+ .want = {.fpcounter = 1012.34},
+ },
+ {
+ .name = "derive",
+ .rate = 1.,
+ .state =
+ {
+ .last_value = {.derive = 1000},
+ .last_time = TIME_T_TO_CDTIME_T(10),
+ .residual = 0,
+ },
+ .type = DS_TYPE_DERIVE,
+ .time = TIME_T_TO_CDTIME_T(20),
+ .want = {.derive = 1010},
+ },
+ {
+ .name = "derive initialization with negative rate",
+ .rate = 1.05,
+ .state = {.last_time = 0},
+ .type = DS_TYPE_DERIVE,
+ .time = TIME_T_TO_CDTIME_T(20),
+ .want_err = EAGAIN,
+ .want_residual = .5,
+ },
+ {
+ .name = "derive with negative rate",
+ .rate = -1.,
+ .state =
+ {
+ .last_value = {.derive = 1000},
+ .last_time = TIME_T_TO_CDTIME_T(10),
+ .residual = 0,
+ },
+ .type = DS_TYPE_DERIVE,
+ .time = TIME_T_TO_CDTIME_T(20),
+ .want = {.derive = 990},
+ },
+ {
+ .name = "residual gets rounded down",
+ .rate = -1.01,
+ .state =
+ {
+ .last_value = {.derive = 1000},
+ .last_time = TIME_T_TO_CDTIME_T(10),
+ .residual = 0,
+ },
+ .type = DS_TYPE_DERIVE,
+ .time = TIME_T_TO_CDTIME_T(20),
+ .want = {.derive = 989},
+ .want_residual = .9,
+ },
+ };
+
+ for (size_t i = 0; i < STATIC_ARRAY_SIZE(cases); i++) {
+ printf("## Case %zu %s\n", i, cases[i].name);
+
+ rate_to_value_state_t state = cases[i].state;
+ value_t got = {0};
+ EXPECT_EQ_INT(cases[i].want_err,
+ rate_to_value(&got, cases[i].rate, &state, cases[i].type,
+ cases[i].time));
+ if (cases[i].want_err) {
+ continue;
+ }
+
+ switch (cases[i].type) {
+ case METRIC_TYPE_COUNTER:
+ EXPECT_EQ_UINT64(cases[i].want.counter, got.counter);
+ EXPECT_EQ_UINT64(cases[i].want.counter, state.last_value.counter);
+ break;
+ case METRIC_TYPE_GAUGE:
+ EXPECT_EQ_DOUBLE(cases[i].want.gauge, got.gauge);
+ EXPECT_EQ_DOUBLE(cases[i].want.gauge, state.last_value.gauge);
+ break;
+ case METRIC_TYPE_FPCOUNTER:
+ EXPECT_EQ_DOUBLE(cases[i].want.fpcounter, got.fpcounter);
+ EXPECT_EQ_UINT64(cases[i].want.fpcounter, state.last_value.fpcounter);
+ break;
+ case METRIC_TYPE_UNTYPED:
+ LOG(false, "invalid metric type");
+ break;
+ }
+
+ EXPECT_EQ_UINT64(cases[i].time, state.last_time);
+ EXPECT_EQ_DOUBLE(cases[i].want_residual, state.residual);
+ }
+
+ return 0;
+}
+
DEF_TEST(value_to_rate) {
struct {
+ char *name;
time_t t0;
time_t t1;
- int ds_type;
+ metric_type_t type;
value_t v0;
value_t v1;
gauge_t want;
+ int want_err;
} cases[] = {
- {0, 10, DS_TYPE_DERIVE, {.derive = 0}, {.derive = 1000}, NAN},
- {10, 20, DS_TYPE_DERIVE, {.derive = 1000}, {.derive = 2000}, 100.0},
- {20, 30, DS_TYPE_DERIVE, {.derive = 2000}, {.derive = 1800}, -20.0},
- {0, 10, DS_TYPE_COUNTER, {.counter = 0}, {.counter = 1000}, NAN},
- {10, 20, DS_TYPE_COUNTER, {.counter = 1000}, {.counter = 5000}, 400.0},
- /* 32bit wrap-around. */
- {20,
- 30,
- DS_TYPE_COUNTER,
- {.counter = 4294967238ULL},
- {.counter = 42},
- 10.0},
- /* 64bit wrap-around. */
- {30,
- 40,
- DS_TYPE_COUNTER,
- {.counter = 18446744073709551558ULL},
- {.counter = 42},
- 10.0},
+ {
+ .name = "derive_t init",
+ .t0 = 0,
+ .t1 = 10,
+ .type = DS_TYPE_DERIVE,
+ .v0 = {.derive = 0},
+ .v1 = {.derive = 1000},
+ .want_err = EAGAIN,
+ },
+ {
+ .name = "derive_t increase",
+ .t0 = 10,
+ .t1 = 20,
+ .type = DS_TYPE_DERIVE,
+ .v0 = {.derive = 1000},
+ .v1 = {.derive = 2000},
+ .want = 100.0,
+ },
+ {
+ .name = "derive_t decrease",
+ .t0 = 20,
+ .t1 = 30,
+ .type = DS_TYPE_DERIVE,
+ .v0 = {.derive = 2000},
+ .v1 = {.derive = 1800},
+ .want = -20.0,
+ },
+ {
+ .name = "counter_t init",
+ .t0 = 0,
+ .t1 = 10,
+ .type = METRIC_TYPE_COUNTER,
+ .v0 = {.counter = 0},
+ .v1 = {.counter = 1000},
+ .want_err = EAGAIN,
+ },
+ {
+ .name = "counter_t increase",
+ .t0 = 10,
+ .t1 = 20,
+ .type = METRIC_TYPE_COUNTER,
+ .v0 = {.counter = 1000},
+ .v1 = {.counter = 5000},
+ .want = 400.0,
+ },
+ {
+ .name = "counter_t 32bit wrap-around",
+ .t0 = 20,
+ .t1 = 30,
+ .type = METRIC_TYPE_COUNTER,
+ .v0 = {.counter = 4294967238ULL},
+ .v1 = {.counter = 42},
+ .want = 10.0,
+ },
+ {
+ .name = "counter_t 64bit wrap-around",
+ .t0 = 30,
+ .t1 = 40,
+ .type = METRIC_TYPE_COUNTER,
+ .v0 = {.counter = 18446744073709551558ULL},
+ .v1 = {.counter = 42},
+ .want = 10.0,
+ },
+ {
+ .name = "fpcounter_t init",
+ .t0 = 0,
+ .t1 = 10,
+ .type = METRIC_TYPE_FPCOUNTER,
+ .v0 = {.fpcounter = 0.},
+ .v1 = {.fpcounter = 10.},
+ .want_err = EAGAIN,
+ },
+ {
+ .name = "fpcounter_t increase",
+ .t0 = 10,
+ .t1 = 20,
+ .type = METRIC_TYPE_FPCOUNTER,
+ .v0 = {.fpcounter = 10.},
+ .v1 = {.fpcounter = 50.5},
+ .want = (50.5 - 10.) / (20. - 10.),
+ },
+ {
+ .name = "fpcounter_t reset",
+ .t0 = 20,
+ .t1 = 30,
+ .type = METRIC_TYPE_FPCOUNTER,
+ .v0 = {.fpcounter = 100.0},
+ .v1 = {.fpcounter = 20.0},
+ .want_err = EAGAIN,
+ },
};
for (size_t i = 0; i < STATIC_ARRAY_SIZE(cases); i++) {
+ printf("## Case %zu %s\n", i, cases[i].name);
+
cdtime_t t0 = TIME_T_TO_CDTIME_T(cases[i].t0);
value_to_rate_state_t state = {
.last_value = cases[i].v0,
.last_time = t0,
};
- gauge_t got;
-
- if (cases[i].t0 == 0) {
- EXPECT_EQ_INT(EAGAIN,
- value_to_rate(&got, cases[i].v1, cases[i].ds_type,
- TIME_T_TO_CDTIME_T(cases[i].t1), &state));
+ gauge_t got = 0;
+ EXPECT_EQ_INT(cases[i].want_err,
+ value_to_rate(&got, cases[i].v1, cases[i].type,
+ TIME_T_TO_CDTIME_T(cases[i].t1), &state));
+ if (cases[i].want_err) {
continue;
}
-
- EXPECT_EQ_INT(0, value_to_rate(&got, cases[i].v1, cases[i].ds_type,
- TIME_T_TO_CDTIME_T(cases[i].t1), &state));
EXPECT_EQ_DOUBLE(cases[i].want, got);
}
RUN_TEST(escape_slashes);
RUN_TEST(escape_string);
RUN_TEST(strunescape);
+ RUN_TEST(rate_to_value);
RUN_TEST(value_to_rate);
RUN_TEST(format_values);
RUN_TEST(string_has_suffix);