]> git.ipfire.org Git - thirdparty/collectd.git/commitdiff
Daemon: add "up down counters" in both an integer and floating point variant.
authorFlorian Forster <octo@collectd.org>
Fri, 9 Feb 2024 07:55:30 +0000 (08:55 +0100)
committerFlorian Forster <octo@collectd.org>
Wed, 21 Feb 2024 20:32:04 +0000 (21:32 +0100)
19 files changed:
src/daemon/metric.c
src/daemon/metric.h
src/daemon/utils_cache.c
src/daemon/utils_cache_test.c
src/utils/cmds/putmetric.c
src/utils/common/common.c
src/utils/common/common.h
src/utils/common/common_test.c
src/utils/format_graphite/format_graphite.c
src/utils/format_influxdb/format_influxdb.c
src/utils/format_json/format_json.c
src/utils/format_json/open_telemetry.c
src/utils/format_kairosdb/format_kairosdb.c
src/utils/format_open_telemetry/format_open_telemetry.cc
src/utils/format_stackdriver/format_stackdriver.c
src/utils/value_list/value_list.c
src/utils/value_list/value_list.h
src/write_prometheus.c
src/write_prometheus_test.c

index 2247220ea70e0e2924a86741d93a7d42f15bb5fb..81df3f46d22e0e78474828f71fcf0b6fefbd3cdb 100644 (file)
 int value_marshal_text(strbuf_t *buf, value_t v, metric_type_t type) {
   switch (type) {
   case METRIC_TYPE_GAUGE:
-  case METRIC_TYPE_FPCOUNTER:
+    if (isnan(v.gauge)) {
+      return strbuf_print(buf, "nan");
+    }
     return strbuf_printf(buf, GAUGE_FORMAT, v.gauge);
   case METRIC_TYPE_COUNTER:
     return strbuf_printf(buf, "%" PRIu64, v.counter);
-  default:
-    ERROR("Unknown metric value type: %d", (int)type);
-    return EINVAL;
+  case METRIC_TYPE_FPCOUNTER:
+    return strbuf_printf(buf, GAUGE_FORMAT, v.fpcounter);
+  case METRIC_TYPE_UP_DOWN_COUNTER:
+    return strbuf_printf(buf, "%" PRId64, v.up_down_counter);
+  case METRIC_TYPE_UP_DOWN_COUNTER_FP:
+    if (isnan(v.up_down_counter_fp)) {
+      return strbuf_print(buf, "nan");
+    }
+    return strbuf_printf(buf, GAUGE_FORMAT, v.up_down_counter_fp);
+  case METRIC_TYPE_UNTYPED:
+    break;
   }
+  ERROR("Unknown metric value type: %d", (int)type);
+  return EINVAL;
 }
 
 static int label_name_compare(void const *a, void const *b) {
index 0cbc50882fa285c42cb974f3247386b6b63f2fbc..99d15a2c3263cbb22bd6e000595a715eba15f5ea 100644 (file)
 
 #define METRIC_ATTR_DOUBLE 0x01
 #define METRIC_ATTR_CUMULATIVE 0x02
+#define METRIC_ATTR_MONOTONIC 0x04
 
 typedef enum {
   METRIC_TYPE_UNTYPED = 0,
+  // METRIC_TYPE_GAUGE are absolute metrics that cannot (meaningfully) be summed
+  // up. Examples are temperatures and utilization ratios.
   METRIC_TYPE_GAUGE = METRIC_ATTR_DOUBLE,
-  METRIC_TYPE_COUNTER = METRIC_ATTR_CUMULATIVE,
-  METRIC_TYPE_FPCOUNTER = METRIC_ATTR_DOUBLE | METRIC_ATTR_CUMULATIVE,
+  // METRIC_TYPE_COUNTER are monotonically increasing integer counts. The rate
+  // of change is meaningful, the absolute value is not.
+  METRIC_TYPE_COUNTER = METRIC_ATTR_CUMULATIVE | METRIC_ATTR_MONOTONIC,
+  // METRIC_TYPE_FPCOUNTER are monotonically increasing floating point counts.
+  // The rate of change is meaningful, the absolute value is not.
+  METRIC_TYPE_FPCOUNTER =
+      METRIC_ATTR_DOUBLE | METRIC_ATTR_CUMULATIVE | METRIC_ATTR_MONOTONIC,
+  // METRIC_TYPE_UP_DOWN_COUNTER are absolute integer metrics that can
+  // (meaningfully) be summed up. Examples are filesystem space used and
+  // physical memory.
+  METRIC_TYPE_UP_DOWN_COUNTER = METRIC_ATTR_CUMULATIVE,
+  // METRIC_TYPE_UP_DOWN_COUNTER_FP are absolute floating point metrics that can
+  // (meaningfully) be summed up.
+  METRIC_TYPE_UP_DOWN_COUNTER_FP = METRIC_ATTR_DOUBLE | METRIC_ATTR_CUMULATIVE,
 } metric_type_t;
 
-#define IS_CUMULATIVE(t) ((t)&METRIC_ATTR_CUMULATIVE)
+#define METRIC_TYPE_TO_STRING(t)                                               \
+  (t == METRIC_TYPE_GAUGE)                ? "gauge"                            \
+  : (t == METRIC_TYPE_COUNTER)            ? "counter"                          \
+  : (t == METRIC_TYPE_FPCOUNTER)          ? "fpcounter"                        \
+  : (t == METRIC_TYPE_UP_DOWN_COUNTER)    ? "up_down_counter"                  \
+  : (t == METRIC_TYPE_UP_DOWN_COUNTER_FP) ? "up_down_counter_fp"               \
+                                          : "unknown"
 
+#define IS_DOUBLE(t) ((t)&METRIC_ATTR_DOUBLE)
+#define IS_MONOTONIC(t) ((t)&METRIC_ATTR_MONOTONIC)
+
+typedef double gauge_t;
 typedef uint64_t counter_t;
 typedef double fpcounter_t;
-typedef double gauge_t;
 typedef int64_t derive_t;
+typedef int64_t up_down_counter_t;
+typedef double up_down_counter_fp_t;
 
 union value_u {
+  gauge_t gauge;
   counter_t counter;
   fpcounter_t fpcounter;
-  gauge_t gauge;
+  up_down_counter_t up_down_counter;
+  up_down_counter_fp_t up_down_counter_fp;
+  // For collectd 5 compatiblity. Treated the same as up_down_counter.
   derive_t derive;
 };
 typedef union value_u value_t;
index ea0a1a7dbdf4a82737f79e4d8598fdd675d72bd1..6662b14caeb5512c5f5d69ce13fa59f9351c1d38 100644 (file)
@@ -281,6 +281,11 @@ int uc_check_timeout(void) {
 
 static int uc_update_rate(metric_t const *m, cache_entry_t *ce) {
   switch (m->family->type) {
+  case METRIC_TYPE_GAUGE: {
+    ce->values_gauge = m->value.gauge;
+    return 0;
+  }
+
   case METRIC_TYPE_COUNTER: {
     // Counter overflows and counter resets are signaled to plugins by resetting
     // "first_time". Since we can't distinguish between an overflow and a
@@ -313,8 +318,13 @@ static int uc_update_rate(metric_t const *m, cache_entry_t *ce) {
     return 0;
   }
 
-  case METRIC_TYPE_GAUGE: {
-    ce->values_gauge = m->value.gauge;
+  case METRIC_TYPE_UP_DOWN_COUNTER: {
+    ce->values_gauge = (gauge_t) m->value.up_down_counter;
+    return 0;
+  }
+
+  case METRIC_TYPE_UP_DOWN_COUNTER_FP: {
+    ce->values_gauge = (gauge_t) m->value.up_down_counter_fp;
     return 0;
   }
 
@@ -465,9 +475,21 @@ int uc_get_rate(metric_t const *m, gauge_t *ret) {
   if (m == NULL || m->family == NULL || ret == NULL) {
     return EINVAL;
   }
-  if (m->family->type == METRIC_TYPE_GAUGE) {
+  switch (m->family->type) {
+  case METRIC_TYPE_GAUGE:
     *ret = m->value.gauge;
     return 0;
+  case METRIC_TYPE_COUNTER:
+  case METRIC_TYPE_FPCOUNTER:
+    break;
+  case METRIC_TYPE_UP_DOWN_COUNTER:
+    *ret = (gauge_t)m->value.up_down_counter;
+    return 0;
+  case METRIC_TYPE_UP_DOWN_COUNTER_FP:
+    *ret = (gauge_t)m->value.up_down_counter_fp;
+    return 0;
+  case METRIC_TYPE_UNTYPED:
+    return EINVAL;
   }
 
   strbuf_t buf = STRBUF_CREATE;
index ef2c66fd4de5c879790f29f529e006f555b64a1c..e05729bf8f60db557fb14ebeda741933258cd1a0 100644 (file)
@@ -104,6 +104,42 @@ DEF_TEST(uc_get_rate) {
           .type = METRIC_TYPE_FPCOUNTER,
           .want = NAN,
       },
+      {
+          .name = "up_down_counter",
+          .first_value = (value_t){.up_down_counter = 10},
+          .second_value = (value_t){.up_down_counter = 20},
+          .first_time = TIME_T_TO_CDTIME_T(100),
+          .second_time = TIME_T_TO_CDTIME_T(110),
+          .type = METRIC_TYPE_UP_DOWN_COUNTER,
+          .want = 20,
+      },
+      {
+          .name = "decreasing up_down_counter",
+          .first_value = (value_t){.up_down_counter = 1000},
+          .second_value = (value_t){.up_down_counter = 215},
+          .first_time = TIME_T_TO_CDTIME_T(100),
+          .second_time = TIME_T_TO_CDTIME_T(110),
+          .type = METRIC_TYPE_UP_DOWN_COUNTER,
+          .want = 215,
+      },
+      {
+          .name = "up_down_counter_fp",
+          .first_value = (value_t){.up_down_counter_fp = 1.0},
+          .second_value = (value_t){.up_down_counter_fp = 2.0},
+          .first_time = TIME_T_TO_CDTIME_T(100),
+          .second_time = TIME_T_TO_CDTIME_T(110),
+          .type = METRIC_TYPE_UP_DOWN_COUNTER_FP,
+          .want = 2.0,
+      },
+      {
+          .name = "decreasing up_down_counter_fp",
+          .first_value = (value_t){.up_down_counter_fp = 100.0},
+          .second_value = (value_t){.up_down_counter_fp = 21.5},
+          .first_time = TIME_T_TO_CDTIME_T(100),
+          .second_time = TIME_T_TO_CDTIME_T(110),
+          .type = METRIC_TYPE_UP_DOWN_COUNTER_FP,
+          .want = 21.5,
+      },
   };
 
   for (size_t i = 0; i < STATIC_ARRAY_SIZE(cases); i++) {
@@ -130,9 +166,11 @@ DEF_TEST(uc_get_rate) {
     EXPECT_EQ_INT(0, uc_update(&fam));
     gauge_t got = 0;
     EXPECT_EQ_INT(0, uc_get_rate(&m, &got));
-    gauge_t want = NAN;
-    if (fam.type == METRIC_TYPE_GAUGE) {
-      want = cases[i].first_value.gauge;
+    gauge_t want = cases[i].first_value.gauge;
+    if (IS_MONOTONIC(fam.type)) {
+      want = NAN;
+    } else if (fam.type == METRIC_TYPE_UP_DOWN_COUNTER) {
+      want = (gauge_t)cases[i].first_value.up_down_counter;
     }
     EXPECT_EQ_DOUBLE(want, got);
 
index 11969860e6590dde4695a572e500a089578a28e3..39adc59338e59651069b8db3a5d5c646f3a50558 100644 (file)
@@ -48,6 +48,10 @@ static int set_option(metric_t *m, char const *key, char const *value,
       m->family->type = METRIC_TYPE_COUNTER;
     } else if (strcasecmp("FPCOUNTER", value) == 0) {
       m->family->type = METRIC_TYPE_FPCOUNTER;
+    } else if (strcasecmp("UP_DOWN_COUNTER", value) == 0) {
+      m->family->type = METRIC_TYPE_UP_DOWN_COUNTER;
+    } else if (strcasecmp("UP_DOWN_COUNTER_FP", value) == 0) {
+      m->family->type = METRIC_TYPE_UP_DOWN_COUNTER_FP;
     } else {
       return CMD_ERROR;
     }
@@ -253,6 +257,12 @@ int cmd_format_putmetric(strbuf_t *buf, metric_t const *m) { /* {{{ */
   case METRIC_TYPE_GAUGE:
     strbuf_print(buf, " type=GAUGE");
     break;
+  case METRIC_TYPE_UP_DOWN_COUNTER:
+    strbuf_print(buf, " type=UP_DOWN_COUNTER");
+    break;
+  case METRIC_TYPE_UP_DOWN_COUNTER_FP:
+    strbuf_print(buf, " type=UP_DOWN_COUNTER_FP");
+    break;
   case METRIC_TYPE_UNTYPED:
     /* no op */
     break;
index 5ad0328f1f1a330319f0fb08707b93255eae9301..61894e052ff2f90bb1be0918beceb6e1f159c0c8 100644 (file)
@@ -933,115 +933,87 @@ int format_name(char *ret, int ret_len, const char *hostname,
 } /* int format_name */
 
 int format_values(strbuf_t *buf, metric_t const *m, bool store_rates) {
-  strbuf_printf(buf, "%.3f", CDTIME_T_TO_DOUBLE(m->time));
-
-  if (store_rates) {
-    gauge_t rates = NAN;
-    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);
-    }
-    return 0;
-  }
+  strbuf_printf(buf, "%.3f:", CDTIME_T_TO_DOUBLE(m->time));
 
-  if (m->family->type == DS_TYPE_DERIVE) {
-    return strbuf_printf(buf, ":%" PRIi64, m->value.derive);
+  if (!store_rates) {
+    return value_marshal_text(buf, m->value, m->family->type);
   }
 
-  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;
+  gauge_t rates = NAN;
+  int err = uc_get_rate(m, &rates);
+  if (err) {
+    ERROR("format_values: uc_get_rate failed: %s", STRERROR(err));
+    return err;
   }
-  ERROR("format_values: Unknown metric type: %d", m->family->type);
-  return EINVAL;
+  if (isnan(rates)) {
+    strbuf_print(buf, "nan");
+  } else {
+    strbuf_printf(buf, GAUGE_FORMAT, rates);
+  }
+  return 0;
 } /* }}} int format_values */
 
-int parse_value(const char *value_orig, value_t *ret_value, int ds_type) {
-  char *value;
+int parse_value(char const *value, value_t *ret_value, metric_type_t type) {
   char *endptr = NULL;
-  size_t value_len;
-
-  if (value_orig == NULL)
-    return EINVAL;
-
-  value = strdup(value_orig);
-  if (value == NULL)
-    return ENOMEM;
-  value_len = strlen(value);
-
-  while ((value_len > 0) && isspace((int)value[value_len - 1])) {
-    value[value_len - 1] = '\0';
-    value_len--;
-  }
+  switch (type) {
+  case METRIC_TYPE_GAUGE:
+    ret_value->gauge = (gauge_t)strtod(value, &endptr);
+    break;
 
-  switch (ds_type) {
-  case DS_TYPE_COUNTER:
+  case METRIC_TYPE_COUNTER:
     ret_value->counter = (counter_t)strtoull(value, &endptr, 0);
     break;
 
-  case DS_TYPE_GAUGE:
-    ret_value->gauge = (gauge_t)strtod(value, &endptr);
+  case METRIC_TYPE_FPCOUNTER:
+    ret_value->fpcounter = (fpcounter_t)strtod(value, &endptr);
     break;
 
-  case DS_TYPE_DERIVE:
-    ret_value->derive = (derive_t)strtoll(value, &endptr, 0);
+  case METRIC_TYPE_UP_DOWN_COUNTER:
+    ret_value->up_down_counter = (up_down_counter_t)strtoll(value, &endptr, 0);
     break;
 
-  default:
-    sfree(value);
-    P_ERROR("parse_value: Invalid data source type: %i.", ds_type);
-    return -1;
+  case METRIC_TYPE_UP_DOWN_COUNTER_FP:
+    ret_value->up_down_counter_fp =
+        (up_down_counter_fp_t)strtod(value, &endptr);
+    break;
+
+  case METRIC_TYPE_UNTYPED:
+    P_ERROR("parse_value: invalid metric type: %d.", type);
+    return EINVAL;
   }
 
   if (value == endptr) {
     P_ERROR("parse_value: Failed to parse string as %s: \"%s\".",
-            DS_TYPE_TO_STRING(ds_type), value);
-    sfree(value);
-    return -1;
-  } else if ((NULL != endptr) && ('\0' != *endptr))
-    P_INFO("parse_value: Ignoring trailing garbage \"%s\" after %s value. "
-           "Input string was \"%s\".",
-           endptr, DS_TYPE_TO_STRING(ds_type), value_orig);
+            METRIC_TYPE_TO_STRING(type), value);
+    return EINVAL;
+  }
+  if (NULL != endptr) {
+    if (*endptr != 0 && !isspace(*endptr)) {
+      P_INFO("parse_value: Ignoring trailing garbage \"%s\" after %s value. "
+             "Input string was \"%s\".",
+             endptr, METRIC_TYPE_TO_STRING(type), value);
+    }
+  }
 
-  sfree(value);
   return 0;
 } /* int parse_value */
 
-int parse_value_file(char const *path, value_t *ret_value, int ds_type) {
-  FILE *fh;
-  char buffer[256];
-
-  fh = fopen(path, "r");
-  if (fh == NULL)
-    return -1;
+int parse_value_file(char const *path, value_t *ret_value, metric_type_t type) {
+  FILE *fh = fopen(path, "r");
+  if (fh == NULL) {
+    return errno;
+  }
 
+  char buffer[256];
   if (fgets(buffer, sizeof(buffer), fh) == NULL) {
     fclose(fh);
-    return -1;
+    return EINVAL;
   }
 
   fclose(fh);
-
   strstripnewline(buffer);
 
-  return parse_value(buffer, ret_value, ds_type);
+  return parse_value(buffer, ret_value, type);
 } /* int parse_value_file */
 
 #if !HAVE_GETPWNAM_R
@@ -1227,9 +1199,6 @@ counter_t counter_diff(counter_t old_value, counter_t new_value) {
 int rate_to_value(value_t *ret_value, gauge_t rate, /* {{{ */
                   rate_to_value_state_t *state, metric_type_t type,
                   cdtime_t t) {
-  gauge_t delta_gauge;
-  cdtime_t delta_t;
-
   if (type == METRIC_TYPE_GAUGE) {
     state->last_value.gauge = rate;
     state->last_time = t;
@@ -1252,19 +1221,12 @@ int rate_to_value(value_t *ret_value, gauge_t rate, /* {{{ */
     return EINVAL;
   }
 
-  delta_t = t - state->last_time;
-  delta_gauge = (rate * CDTIME_T_TO_DOUBLE(delta_t)) + state->residual;
+  cdtime_t delta_t = t - state->last_time;
+  gauge_t delta_gauge = (rate * CDTIME_T_TO_DOUBLE(delta_t)) + state->residual;
 
   /* Previous value is invalid. */
   if (state->last_time == 0) /* {{{ */
   {
-    if (type == DS_TYPE_DERIVE) {
-      state->last_value.derive = (derive_t)floor(rate);
-      state->residual = rate - ((gauge_t)state->last_value.derive);
-      state->last_time = t;
-      return EAGAIN;
-    }
-
     switch (type) {
     case METRIC_TYPE_GAUGE:
       /* not reached */
@@ -1277,6 +1239,14 @@ int rate_to_value(value_t *ret_value, gauge_t rate, /* {{{ */
       state->last_value.fpcounter = (fpcounter_t)rate;
       state->residual = 0;
       break;
+    case METRIC_TYPE_UP_DOWN_COUNTER:
+      state->last_value.up_down_counter = (up_down_counter_t)floor(rate);
+      state->residual = rate - ((gauge_t)state->last_value.up_down_counter);
+      break;
+    case METRIC_TYPE_UP_DOWN_COUNTER_FP:
+      state->last_value.up_down_counter_fp = (up_down_counter_fp_t)rate;
+      state->residual = 0;
+      break;
     case METRIC_TYPE_UNTYPED:
       ERROR("rate_to_value: invalid metric type: %d", type);
       return EINVAL;
@@ -1286,29 +1256,31 @@ int rate_to_value(value_t *ret_value, gauge_t rate, /* {{{ */
     return EAGAIN;
   } /* }}} */
 
-  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);
-
-    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);
+    counter_t delta = (counter_t)delta_gauge;
+    state->last_value.counter += delta;
+    state->residual = delta_gauge - ((gauge_t)delta);
     break;
   }
   case METRIC_TYPE_FPCOUNTER: {
-    fpcounter_t delta_counter = (fpcounter_t)delta_gauge;
-    state->last_value.fpcounter += delta_counter;
+    fpcounter_t delta = (fpcounter_t)delta_gauge;
+    state->last_value.fpcounter += delta;
+    state->residual = 0;
+    break;
+  }
+  case METRIC_TYPE_UP_DOWN_COUNTER: {
+    up_down_counter_t delta = (up_down_counter_t)floor(delta_gauge);
+    state->last_value.up_down_counter += delta;
+    state->residual = delta_gauge - ((gauge_t)delta);
+    break;
+  }
+  case METRIC_TYPE_UP_DOWN_COUNTER_FP: {
+    up_down_counter_fp_t delta = (up_down_counter_fp_t)delta_gauge;
+    state->last_value.up_down_counter_fp += delta;
     state->residual = 0;
     break;
   }
@@ -1324,17 +1296,10 @@ int rate_to_value(value_t *ret_value, gauge_t rate, /* {{{ */
 
 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: {
+  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);
@@ -1349,6 +1314,18 @@ static int calculate_rate(gauge_t *ret_rate, value_t value, metric_type_t type,
     *ret_rate = ((gauge_t)diff) / ((gauge_t)interval);
     return 0;
   }
+  case METRIC_TYPE_UP_DOWN_COUNTER: {
+    up_down_counter_t diff =
+        value.up_down_counter - state->last_value.up_down_counter;
+    *ret_rate = ((gauge_t)diff) / ((gauge_t)interval);
+    return 0;
+  }
+  case METRIC_TYPE_UP_DOWN_COUNTER_FP: {
+    up_down_counter_fp_t diff =
+        value.up_down_counter_fp - state->last_value.up_down_counter_fp;
+    *ret_rate = ((gauge_t)diff) / ((gauge_t)interval);
+    return 0;
+  }
   case METRIC_TYPE_UNTYPED:
     break;
   }
index a8899343eb5e0f5ea2cbf48396b53af271db5134..99bbb289093fd03d3070a719c2ef72ef53ca470e 100644 (file)
@@ -341,13 +341,13 @@ int format_name(char *ret, int ret_len, const char *hostname,
               (vl)->type, (vl)->type_instance)
 int format_values(strbuf_t *buf, metric_t const *m, bool store_rates);
 
-int parse_value(const char *value, value_t *ret_value, int ds_type);
+int parse_value(const char *value, value_t *ret_value, metric_type_t type);
 
 /* parse_value_file reads "path" and parses its content as an integer or
  * floating point, depending on "ds_type". On success, the value is stored in
  * "ret_value" and zero is returned. On failure, a non-zero value is returned.
  */
-int parse_value_file(char const *path, value_t *ret_value, int ds_type);
+int parse_value_file(char const *path, value_t *ret_value, metric_type_t type);
 
 #if !HAVE_GETPWNAM_R
 struct passwd;
index 80ba765712a58cfa381d38001608905a286ed4aa..b643c2c97b981675f4bfbb7102f163431f32c773 100644 (file)
@@ -436,18 +436,26 @@ DEF_TEST(rate_to_value) {
     }
 
     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_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_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_UP_DOWN_COUNTER:
+      EXPECT_EQ_UINT64(cases[i].want.up_down_counter, got.up_down_counter);
+      EXPECT_EQ_UINT64(cases[i].want.up_down_counter, state.last_value.up_down_counter);
+      break;
+    case METRIC_TYPE_UP_DOWN_COUNTER_FP:
+      EXPECT_EQ_DOUBLE(cases[i].want.up_down_counter_fp, got.up_down_counter_fp);
+      EXPECT_EQ_UINT64(cases[i].want.up_down_counter_fp, state.last_value.up_down_counter_fp);
+      break;
     case METRIC_TYPE_UNTYPED:
       LOG(false, "invalid metric type");
       break;
index 1f466bf7f86e0931889061635afd5bf92e006338..73e776221232d3071e1be812eb8bb0c862ce0710 100644 (file)
@@ -44,27 +44,10 @@ static int format_double(strbuf_t *buf, double d) {
 
 static int gr_format_values(strbuf_t *buf, metric_t const *m, gauge_t rate,
                             bool store_rate) {
-  if (m->family->type == METRIC_TYPE_GAUGE) {
-    rate = m->value.gauge;
-  }
-
   if (store_rate) {
     return format_double(buf, rate);
   }
-
-  switch (m->family->type) {
-  case METRIC_TYPE_COUNTER:
-    return strbuf_printf(buf, "%" PRIu64, m->value.counter);
-  case METRIC_TYPE_FPCOUNTER:
-    return format_double(buf, m->value.fpcounter);
-  case METRIC_TYPE_GAUGE:
-    return format_double(buf, m->value.gauge);
-  case METRIC_TYPE_UNTYPED:
-    break;
-  }
-
-  P_ERROR("gr_format_values: Unknown data source type: %d", m->family->type);
-  return EINVAL;
+  return value_marshal_text(buf, m->value, m->family->type);
 }
 
 static int graphite_print_escaped(strbuf_t *buf, char const *s,
index 2735352e630cae9bf9e76cc3e6964d3365c6c783..2e6d5187278cf7aaebef4fc7edcbaf5054a576ea 100644 (file)
@@ -73,22 +73,18 @@ static int format_metric_value(strbuf_t *sb, metric_t const *m,
     return format_metric_rate(sb, m);
   }
 
-  switch (m->family->type) {
-  case METRIC_TYPE_GAUGE:
-    if (isnan(m->value.gauge)) {
-      return EAGAIN;
-    }
-    return strbuf_printf(sb, "value=" GAUGE_FORMAT, m->value.gauge);
-  case METRIC_TYPE_COUNTER:
-    return strbuf_printf(sb, "value=%" PRIu64 "i", m->value.counter);
-  case METRIC_TYPE_FPCOUNTER:
-    return strbuf_printf(sb, "value=" GAUGE_FORMAT, m->value.fpcounter);
-  case METRIC_TYPE_UNTYPED:
-    break;
+  if ((m->family->type == METRIC_TYPE_GAUGE && isnan(m->value.gauge)) ||
+      (m->family->type == METRIC_TYPE_UP_DOWN_COUNTER_FP &&
+       isnan(m->value.up_down_counter_fp))) {
+    return EAGAIN;
   }
 
-  ERROR("format_influxdb plugin: invalid metric type: %d", m->family->type);
-  return EINVAL;
+  int err = strbuf_print(sb, "value=");
+  ERR_COMBINE(err, value_marshal_text(sb, m->value, m->family->type));
+  if (!IS_DOUBLE(m->family->type)) {
+    ERR_COMBINE(err, strbuf_print(sb, "i"));
+  }
+  return err;
 }
 
 static int format_metric_time(strbuf_t *sb, metric_t const *m) {
index c65ab97bfd9548d5296b1f3483eb656432b66b8f..3fe237accdcee12f7f76343916e74c6f885b2e3f 100644 (file)
  **/
 
 #include "collectd.h"
-
-#include "utils/format_json/format_json.h"
-
-#include "plugin.h"
+#include "daemon/plugin.h"
+#include "daemon/utils_cache.h"
 #include "utils/common/common.h"
-#include "utils_cache.h"
+#include "utils/format_json/format_json.h"
 
 #include <yajl/yajl_common.h>
 #include <yajl/yajl_gen.h>
@@ -159,6 +157,24 @@ static int format_metric(yajl_gen g, metric_t const *m) {
   return 0;
 }
 
+static char const *metric_type_to_string(metric_type_t type) {
+  switch (type) {
+  case METRIC_TYPE_GAUGE:
+    return "GAUGE";
+  case METRIC_TYPE_COUNTER:
+    return "COUNTER";
+  case METRIC_TYPE_FPCOUNTER:
+    return "FPCOUNTER";
+  case METRIC_TYPE_UP_DOWN_COUNTER:
+    return "UP_DOWN_COUNTER";
+  case METRIC_TYPE_UP_DOWN_COUNTER_FP:
+    return "UP_DOWN_COUNTER_FP";
+  case METRIC_TYPE_UNTYPED:
+    break;
+  }
+  return "INVALID";
+}
+
 /* json_metric_family that all metrics in ml have the same name and value_type.
  *
  * Example:
@@ -181,24 +197,8 @@ static int json_metric_family(yajl_gen g, metric_family_t const *fam) {
   CHECK(json_add_string(g, "name"));
   CHECK(json_add_string(g, fam->name));
 
-  char const *type = NULL;
-  switch (fam->type) {
-  /* TODO(octo): handle store_rates. */
-  case METRIC_TYPE_GAUGE:
-    type = "GAUGE";
-    break;
-  case METRIC_TYPE_COUNTER:
-    type = "COUNTER";
-    break;
-  case METRIC_TYPE_FPCOUNTER:
-    type = "FPCOUNTER";
-    break;
-  case METRIC_TYPE_UNTYPED:
-    type = "UNTYPED";
-    break;
-  }
   CHECK(json_add_string(g, "type"));
-  CHECK(json_add_string(g, type));
+  CHECK(json_add_string(g, metric_type_to_string(fam->type)));
 
   CHECK(json_add_string(g, "metrics"));
   CHECK(yajl_gen_array_open(g));
@@ -216,6 +216,7 @@ static int json_metric_family(yajl_gen g, metric_family_t const *fam) {
   return 0;
 }
 
+/* TODO(octo): handle store_rates. */
 int format_json_metric_family(strbuf_t *buf, metric_family_t const *fam,
                               bool store_rates) {
   if ((buf == NULL) || (fam == NULL))
index 3b5ac6ba3d3aa1ed3159474a524d001d7a16659f..e5e9ec5c5b08027a5a06e5831831dacfae3c5926 100644 (file)
@@ -92,17 +92,25 @@ static int number_data_point(yajl_gen g, metric_t const *m) {
   CHECK(yajl_gen_integer(g, CDTIME_T_TO_NS(m->time)));
 
   switch (m->family->type) {
+  case METRIC_TYPE_GAUGE:
+    CHECK(json_add_string(g, "asDouble"));
+    CHECK(yajl_gen_double(g, m->value.gauge));
+    break;
   case METRIC_TYPE_COUNTER:
     CHECK(json_add_string(g, "asInt"));
-    CHECK(yajl_gen_integer(g, m->value.counter));
+    CHECK(yajl_gen_integer(g, (long long int)m->value.counter));
     break;
   case METRIC_TYPE_FPCOUNTER:
     CHECK(json_add_string(g, "asDouble"));
     CHECK(yajl_gen_double(g, m->value.fpcounter));
     break;
-  case METRIC_TYPE_GAUGE:
+  case METRIC_TYPE_UP_DOWN_COUNTER:
+    CHECK(json_add_string(g, "asInt"));
+    CHECK(yajl_gen_integer(g, (long long int)m->value.up_down_counter));
+    break;
+  case METRIC_TYPE_UP_DOWN_COUNTER_FP:
     CHECK(json_add_string(g, "asDouble"));
-    CHECK(yajl_gen_double(g, m->value.gauge));
+    CHECK(yajl_gen_integer(g, m->value.up_down_counter_fp));
     break;
   case METRIC_TYPE_UNTYPED:
     ERROR("format_json_open_telemetry: Unexpected metric type: %d",
@@ -166,15 +174,17 @@ static int metric(yajl_gen g, metric_family_t const *fam) {
   }
 
   switch (fam->type) {
+  case METRIC_TYPE_GAUGE:
+    CHECK(json_add_string(g, "gauge"));
+    CHECK(gauge(g, fam));
+    break;
   case METRIC_TYPE_COUNTER:
   case METRIC_TYPE_FPCOUNTER:
+  case METRIC_TYPE_UP_DOWN_COUNTER:
+  case METRIC_TYPE_UP_DOWN_COUNTER_FP:
     CHECK(json_add_string(g, "sum"));
     CHECK(sum(g, fam));
     break;
-  case METRIC_TYPE_GAUGE:
-    CHECK(json_add_string(g, "gauge"));
-    CHECK(gauge(g, fam));
-    break;
   case METRIC_TYPE_UNTYPED:
     ERROR("format_json_open_telemetry: Unexpected metric type: %d", fam->type);
     return EINVAL;
index d8f92a19502d1eee358e66a1e0281b0f20bd3a73..a3c334e76b7ac2f39d89b2ab7e8cf25c2a92cec2 100644 (file)
@@ -114,11 +114,17 @@ static int json_add_value(yajl_gen g, metric_t const *m,
     }
     return 0;
   }
+  case METRIC_TYPE_COUNTER:
+    CHECK(yajl_gen_integer(g, (long long int)m->value.counter));
+    return 0;
   case METRIC_TYPE_FPCOUNTER:
     CHECK(yajl_gen_double(g, m->value.fpcounter));
     return 0;
-  case METRIC_TYPE_COUNTER:
-    CHECK(yajl_gen_integer(g, (long long int)m->value.counter));
+  case METRIC_TYPE_UP_DOWN_COUNTER:
+    CHECK(yajl_gen_integer(g, (long long int)m->value.up_down_counter));
+    return 0;
+  case METRIC_TYPE_UP_DOWN_COUNTER_FP:
+    CHECK(yajl_gen_double(g, m->value.up_down_counter_fp));
     return 0;
   case METRIC_TYPE_UNTYPED:
     break;
index 584c2318bc8ca019f13d76fd731f10ab5f1c353b..8cdb4a47027ebdf4e8a4461a97c66087bd30b57f 100644 (file)
@@ -67,12 +67,20 @@ static void metric_to_number_data_point(NumberDataPoint *dp,
 
   // A valid metric type is guaranteed by add_metric().
   switch (m->family->type) {
+  case METRIC_TYPE_GAUGE:
+    dp->set_as_double(m->value.gauge);
+    return;
   case METRIC_TYPE_COUNTER:
-    dp->set_as_int(m->value.derive);
+    dp->set_as_int((int64_t)m->value.counter);
+    return;
+  case METRIC_TYPE_UP_DOWN_COUNTER:
+    dp->set_as_int((int64_t)m->value.up_down_counter);
     return;
-  case METRIC_TYPE_GAUGE:
   case METRIC_TYPE_FPCOUNTER:
-    dp->set_as_double(m->value.gauge);
+    dp->set_as_double(m->value.fpcounter);
+    return;
+  case METRIC_TYPE_UP_DOWN_COUNTER_FP:
+    dp->set_as_double(m->value.up_down_counter_fp);
     return;
   case METRIC_TYPE_UNTYPED:
     // Fall through. This case signals the compiler that we're checking all
@@ -94,7 +102,7 @@ static void set_sum(Metric *m, metric_family_t const *fam) {
   }
 
   s->set_aggregation_temporality(AGGREGATION_TEMPORALITY_CUMULATIVE);
-  s->set_is_monotonic(true);
+  s->set_is_monotonic(IS_MONOTONIC(fam->type));
 }
 
 static void set_gauge(Metric *m, metric_family_t const *fam) {
@@ -125,13 +133,15 @@ static void add_metric(ScopeMetrics *sm, metric_family_t const *fam) {
   }
 
   switch (fam->type) {
+  case METRIC_TYPE_GAUGE:
+    set_gauge(m, fam);
+    return;
   case METRIC_TYPE_COUNTER:
   case METRIC_TYPE_FPCOUNTER:
+  case METRIC_TYPE_UP_DOWN_COUNTER:
+  case METRIC_TYPE_UP_DOWN_COUNTER_FP:
     set_sum(m, fam);
     return;
-  case METRIC_TYPE_GAUGE:
-    set_gauge(m, fam);
-    return;
   case METRIC_TYPE_UNTYPED:
     // Never reached, only here to show the compiler we're handling all possible
     // `metric_type_t` values.
index fe81a04df8461c1e771b733d7b8387a80669c437..2e22328bf6f6d85af126f348d0e5c384f40e111a 100644 (file)
@@ -141,6 +141,20 @@ static int format_typed_value(yajl_gen gen, metric_t const *m,
     }
     break;
   }
+  case METRIC_TYPE_COUNTER: {
+    /* Counter resets are handled in format_time_series(). */
+    assert(m->value.counter >= start_value.counter);
+
+    counter_t diff = m->value.counter - start_value.counter;
+    char integer[64] = {0};
+    ssnprintf(integer, sizeof(integer), "%" PRIu64, diff);
+
+    int status = json_string(gen, "int64Value") || json_string(gen, integer);
+    if (status != 0) {
+      return status;
+    }
+    break;
+  }
   case METRIC_TYPE_FPCOUNTER: {
     /* Counter resets are handled in format_time_series(). */
     assert(m->value.fpcounter >= start_value.fpcounter);
@@ -152,13 +166,9 @@ static int format_typed_value(yajl_gen gen, metric_t const *m,
     }
     break;
   }
-  case METRIC_TYPE_COUNTER: {
-    /* Counter resets are handled in format_time_series(). */
-    assert(m->value.counter >= start_value.counter);
-
-    counter_t diff = m->value.counter - start_value.counter;
+  case METRIC_TYPE_UP_DOWN_COUNTER: {
     char integer[64] = {0};
-    ssnprintf(integer, sizeof(integer), "%" PRIu64, diff);
+    ssnprintf(integer, sizeof(integer), "%" PRId64, m->value.up_down_counter);
 
     int status = json_string(gen, "int64Value") || json_string(gen, integer);
     if (status != 0) {
@@ -166,6 +176,14 @@ static int format_typed_value(yajl_gen gen, metric_t const *m,
     }
     break;
   }
+  case METRIC_TYPE_UP_DOWN_COUNTER_FP: {
+    int status = json_string(gen, "doubleValue") ||
+                 yajl_gen_double(gen, m->value.up_down_counter_fp);
+    if (status != 0) {
+      return status;
+    }
+    break;
+  }
   case METRIC_TYPE_UNTYPED:
     ERROR("format_typed_value: invalid metric type %d.", m->family->type);
     return EINVAL;
@@ -185,6 +203,8 @@ static int format_typed_value(yajl_gen gen, metric_t const *m,
 static int format_metric_kind(yajl_gen gen, metric_t const *m) {
   switch (m->family->type) {
   case METRIC_TYPE_GAUGE:
+  case METRIC_TYPE_UP_DOWN_COUNTER:
+  case METRIC_TYPE_UP_DOWN_COUNTER_FP:
     return json_string(gen, "GAUGE");
   case METRIC_TYPE_COUNTER:
   case METRIC_TYPE_FPCOUNTER:
@@ -207,7 +227,9 @@ static int format_value_type(yajl_gen gen, metric_t const *m) {
   switch (m->family->type) {
   case METRIC_TYPE_GAUGE:
   case METRIC_TYPE_FPCOUNTER:
+  case METRIC_TYPE_UP_DOWN_COUNTER_FP:
     return json_string(gen, "DOUBLE");
+  case METRIC_TYPE_UP_DOWN_COUNTER:
   case METRIC_TYPE_COUNTER:
     return json_string(gen, "INT64");
   case METRIC_TYPE_UNTYPED:
@@ -272,7 +294,7 @@ static int format_time_interval(yajl_gen gen, metric_t const *m,
   if (status != 0)
     return status;
 
-  if (IS_CUMULATIVE(m->family->type)) {
+  if (IS_MONOTONIC(m->family->type)) {
     int status = json_string(gen, "startTime") || json_time(gen, start_time);
     if (status != 0)
       return status;
@@ -391,6 +413,13 @@ static int format_time_series(yajl_gen gen, metric_t const *m,
       return EAGAIN;
     }
     break;
+  case METRIC_TYPE_UP_DOWN_COUNTER:
+    break;
+  case METRIC_TYPE_UP_DOWN_COUNTER_FP:
+    if (!isfinite(m->value.up_down_counter_fp)) {
+      return EAGAIN;
+    }
+    break;
   case METRIC_TYPE_UNTYPED:
     ERROR("format_stackdriver: Invalid metric type: %d", m->family->type);
     return EINVAL;
index 2ca6692e3f34431a78bce4265992ed114b09594b..2c5760b915042d9d74ff207636033196622bcb42 100644 (file)
@@ -396,8 +396,11 @@ plugin_value_list_to_metric_family(value_list_t const *vl, data_set_t const *ds,
   fam->name = strdup(buf.ptr);
   STRBUF_DESTROY(buf);
 
-  fam->type = (ds->ds[index].type == DS_TYPE_GAUGE) ? METRIC_TYPE_GAUGE
-                                                    : METRIC_TYPE_COUNTER;
+  if (ds->ds[index].type == DS_TYPE_GAUGE) {
+    fam->type = METRIC_TYPE_UP_DOWN_COUNTER_FP;
+  } else {
+    fam->type = METRIC_TYPE_COUNTER;
+  }
 
   metric_t m = {
       .family = fam,
index bad84c275d6eefcca95b0ad5aa396c625548e629..68454bbd24bec09f4abadfa891421d22068a96e1 100644 (file)
@@ -34,7 +34,7 @@
 
 #define DS_TYPE_COUNTER METRIC_TYPE_COUNTER
 #define DS_TYPE_GAUGE METRIC_TYPE_GAUGE
-#define DS_TYPE_DERIVE (65536 + METRIC_ATTR_CUMULATIVE + 1)
+#define DS_TYPE_DERIVE METRIC_TYPE_UP_DOWN_COUNTER
 
 #define DS_TYPE_TO_STRING(t)                                                   \
   (t == DS_TYPE_COUNTER)  ? "counter"                                          \
index 2375308b40bac9ecd1fe570cd6a53fe26bf6f2e1..231ee3ebe162f09b908f71ae0907b0b082a2e61c 100644 (file)
@@ -278,7 +278,7 @@ static void format_metric_family_name(strbuf_t *buf,
     strbuf_print_restricted(buf, pfam->unit, VALID_NAME_CHARS, '_');
   }
 
-  if (IS_CUMULATIVE(pfam->type)) {
+  if (IS_MONOTONIC(pfam->type)) {
     strbuf_print(buf, "_total");
   }
 }
@@ -291,6 +291,8 @@ static void format_metric_family(strbuf_t *buf,
   char *type = NULL;
   switch (pfam->type) {
   case METRIC_TYPE_GAUGE:
+  case METRIC_TYPE_UP_DOWN_COUNTER:
+  case METRIC_TYPE_UP_DOWN_COUNTER_FP:
     type = "gauge";
     break;
   case METRIC_TYPE_COUNTER:
@@ -321,11 +323,8 @@ static void format_metric_family(strbuf_t *buf,
     char const *instance = label_set_get(pm->resource, "service.instance.id");
 
     format_metric(buf, pm, family_name.ptr, job, instance);
-
-    if (pfam->type == METRIC_TYPE_COUNTER)
-      strbuf_printf(buf, " %" PRIu64, pm->value.counter);
-    else
-      strbuf_printf(buf, " " GAUGE_FORMAT, pm->value.gauge);
+    strbuf_print(buf, " ");
+    value_marshal_text(buf, pm->value, pfam->type);
 
     if (pm->time > 0) {
       strbuf_printf(buf, " %" PRIi64 "\n", CDTIME_T_TO_MS(pm->time));
index 633e6254318114e7cfe82ca0dd65ef243b5a37ee..86cd8aebcdcc76256274fec816dd38b0c533bf14 100644 (file)
@@ -65,7 +65,7 @@ DEF_TEST(format_metric_family_name) {
   } cases[] = {
       {
           .name = "(lambda).function.executions(#)",
-          .type = METRIC_TYPE_UNTYPED,
+          .type = METRIC_TYPE_GAUGE,
           .want = "lambda_function_executions",
       },
       {
@@ -203,7 +203,7 @@ DEF_TEST(format_metric_family) {
           .pfam =
               {
                   .name = "unit.test",
-                  .type = METRIC_TYPE_UNTYPED,
+                  .type = METRIC_TYPE_GAUGE,
                   .metrics =
                       &(prometheus_metric_t){
                           .label =
@@ -223,7 +223,7 @@ DEF_TEST(format_metric_family) {
                   .metrics_num = 1,
               },
           .want = "# HELP unit_test\n"
-                  "# TYPE unit_test untyped\n"
+                  "# TYPE unit_test gauge\n"
                   "unit_test{job=\"example.com\",instance=\"\",metric_name="
                   "\"unit.test\"} 42\n"
                   "\n",
@@ -233,7 +233,7 @@ DEF_TEST(format_metric_family) {
           .pfam =
               {
                   .name = "unit.test",
-                  .type = METRIC_TYPE_UNTYPED,
+                  .type = METRIC_TYPE_GAUGE,
                   .metrics =
                       &(prometheus_metric_t){
                           .resource =
@@ -256,17 +256,67 @@ DEF_TEST(format_metric_family) {
                                       },
                                   .num = 1,
                               },
-                          .value =
-                              (value_t){
-                                  .gauge = 42,
-                              },
+                          .value.gauge = 42,
+                      },
+                  .metrics_num = 1,
+              },
+          // clang-format off
+          .want =
+            "# HELP unit_test\n"
+            "# TYPE unit_test gauge\n"
+            "unit_test{job=\"service name\",instance=\"service instance id\",metric_name=\"unit.test\"} 42\n"
+            "\n",
+          // clang-format on
+      },
+      {
+          .name = "METRIC_TYPE_FPCOUNTER",
+          .pfam =
+              {
+                  .name = "unit_test",
+                  .type = METRIC_TYPE_FPCOUNTER,
+                  .metrics =
+                      (prometheus_metric_t[]){
+                          {.value.fpcounter = 42.0},
+                      },
+                  .metrics_num = 1,
+              },
+          .want = "# HELP unit_test_total\n"
+                  "# TYPE unit_test_total counter\n"
+                  "unit_test_total{job=\"example.com\",instance=\"\"} 42\n"
+                  "\n",
+      },
+      {
+          .name = "METRIC_TYPE_UP_DOWN_COUNTER",
+          .pfam =
+              {
+                  .name = "unit_test",
+                  .type = METRIC_TYPE_UP_DOWN_COUNTER,
+                  .metrics =
+                      (prometheus_metric_t[]){
+                          {.value.up_down_counter = 42},
+                      },
+                  .metrics_num = 1,
+              },
+          .want = "# HELP unit_test\n"
+                  "# TYPE unit_test gauge\n"
+                  "unit_test{job=\"example.com\",instance=\"\"} 42\n"
+                  "\n",
+      },
+      {
+          .name = "METRIC_TYPE_UP_DOWN_COUNTER_FP",
+          .pfam =
+              {
+                  .name = "unit_test",
+                  .type = METRIC_TYPE_UP_DOWN_COUNTER_FP,
+                  .metrics =
+                      (prometheus_metric_t[]){
+                          {.value.up_down_counter_fp = 42.0},
                       },
                   .metrics_num = 1,
               },
           .want = "# HELP unit_test\n"
-                  "# TYPE unit_test untyped\n"
-                  "unit_test{job=\"service name\",instance=\"service instance "
-                  "id\",metric_name=\"unit.test\"} 42\n"
+                  "# TYPE unit_test gauge\n"
+                  "unit_test{job=\"example.com\",instance=\"\"} 42\n"
                   "\n",
       },
   };