]> git.ipfire.org Git - thirdparty/collectd.git/commitdiff
write_prometheus plugin: Add an end-to-end test.
authorFlorian Forster <octo@collectd.org>
Mon, 19 Feb 2024 17:24:35 +0000 (18:24 +0100)
committerFlorian Forster <octo@collectd.org>
Tue, 20 Feb 2024 15:00:26 +0000 (16:00 +0100)
src/write_prometheus.c
src/write_prometheus_test.c

index f7913a32b68d05f982dd42b571f0bef1e77dee4b..05eb35e005dfef13134b8757369995a6f645bcfc 100644 (file)
@@ -446,7 +446,8 @@ static void format_metric_families(strbuf_t *buf,
   }
 }
 
-static void format_text(strbuf_t *buf) {
+/* visible for testing */
+void format_text(strbuf_t *buf) {
   pthread_mutex_lock(&prom_metrics_lock);
 
   size_t families_num = (size_t)c_avl_size(prom_metrics);
@@ -731,13 +732,43 @@ static int prom_config(oconfig_item_t *ci) {
   return 0;
 }
 
-static int prom_init(void) {
+/* Visible for testing */
+int alloc_metrics(void) {
+  if (prom_metrics != NULL) {
+    return 0;
+  }
+
+  prom_metrics = c_avl_create((int (*)(const void *, const void *))strcmp);
   if (prom_metrics == NULL) {
-    prom_metrics = c_avl_create((int (*)(const void *, const void *))strcmp);
-    if (prom_metrics == NULL) {
-      ERROR("write_prometheus plugin: c_avl_create() failed.");
-      return -1;
-    }
+    ERROR("write_prometheus plugin: c_avl_create() failed.");
+    return ENOMEM;
+  }
+
+  return 0;
+}
+
+/* Visible for testing */
+void free_metrics(void) {
+  if (prom_metrics == NULL) {
+    return;
+  }
+
+  char *name = NULL;
+  metric_family_t *prom_fam = NULL;
+  while (c_avl_pick(prom_metrics, (void *)&name, (void *)&prom_fam) == 0) {
+    assert(name == prom_fam->name);
+    name = NULL;
+    metric_family_free(prom_fam);
+  }
+
+  c_avl_destroy(prom_metrics);
+  prom_metrics = NULL;
+}
+
+static int prom_init(void) {
+  int err = alloc_metrics();
+  if (err) {
+    return err;
   }
 
   if (httpd == NULL) {
@@ -752,8 +783,9 @@ static int prom_init(void) {
   return 0;
 }
 
-static int prom_write(metric_family_t const *fam,
-                      __attribute__((unused)) user_data_t *ud) {
+/* Visible for testing */
+int prom_write(metric_family_t const *fam,
+               __attribute__((unused)) user_data_t *ud) {
   pthread_mutex_lock(&prom_metrics_lock);
 
   metric_family_t *prom_fam = NULL;
@@ -862,24 +894,14 @@ static int prom_missing(metric_family_t const *fam,
   return 0;
 }
 
-static int prom_shutdown(void) {
+int prom_shutdown(void) {
   if (httpd != NULL) {
     MHD_stop_daemon(httpd);
     httpd = NULL;
   }
 
   pthread_mutex_lock(&prom_metrics_lock);
-  if (prom_metrics != NULL) {
-    char *name;
-    metric_family_t *prom_fam;
-    while (c_avl_pick(prom_metrics, (void *)&name, (void *)&prom_fam) == 0) {
-      assert(name == prom_fam->name);
-      name = NULL;
-      metric_family_free(prom_fam);
-    }
-    c_avl_destroy(prom_metrics);
-    prom_metrics = NULL;
-  }
+  free_metrics();
   pthread_mutex_unlock(&prom_metrics_lock);
 
   sfree(httpd_host);
index 386d1eabef3848412766937c18bc9e30191fa3d5..631f4ff1ae26b4011d6a7783a162e249d62d6804 100644 (file)
@@ -439,11 +439,154 @@ DEF_TEST(target_info) {
   return 0;
 }
 
+void format_text(strbuf_t *buf);
+int prom_write(metric_family_t const *fam, user_data_t *ud);
+int alloc_metrics(void);
+void free_metrics(void);
+
+DEF_TEST(end_to_end) {
+  hostname_set("example.com");
+
+  struct {
+    char const *name;
+    metric_family_t *fams;
+    size_t fams_num;
+    char const *want;
+  } cases[] = {
+      {
+          .name = "single metric",
+          .fams =
+              &(metric_family_t){
+                  .name = "unit.test",
+                  .type = METRIC_TYPE_COUNTER,
+                  .resource =
+                      {
+                          .ptr =
+                              (label_pair_t[]){
+                                  {"host.name", "example.org"},
+                                  {"service.instance.id", "instance1"},
+                                  {"service.name", "name1"},
+                              },
+                          .num = 3,
+                      },
+                  .metric =
+                      {
+                          .ptr =
+                              &(metric_t){
+                                  .value.counter = 42,
+                              },
+                          .num = 1,
+                      },
+              },
+          .fams_num = 1,
+// clang-format off
+          .want =
+            "# HELP target_info Target metadata\n"
+           "# TYPE target_info gauge\n"
+           "target_info{job=\"name1\",instance=\"instance1\",host_name=\"example.org\"} 1\n"
+           "\n"
+           "# HELP unit_test_total\n"
+           "# TYPE unit_test_total counter\n"
+           "unit_test_total{job=\"name1\",instance=\"instance1\"} 42\n"
+           "\n"
+           "# collectd/write_prometheus " PACKAGE_VERSION
+           " at example.com\n",
+// clang-format on
+      },
+      {
+          .name = "multiple resources",
+          .fams =
+              (metric_family_t[]){
+                  {
+                      .name = "unit.test",
+                      .type = METRIC_TYPE_COUNTER,
+                      .resource =
+                          {
+                              .ptr =
+                                  (label_pair_t[]){
+                                      {"host.name", "example.org"},
+                                      {"service.instance.id", "instance1"},
+                                      {"service.name", "name1"},
+                                  },
+                              .num = 3,
+                          },
+                      .metric =
+                          {
+                              .ptr =
+                                  &(metric_t){
+                                      .value.counter = 42,
+                                  },
+                              .num = 1,
+                          },
+                  },
+                  {
+                      .name = "unit.test",
+                      .type = METRIC_TYPE_COUNTER,
+                      .resource =
+                          {
+                              .ptr =
+                                  (label_pair_t[]){
+                                      {"host.name", "example.net"},
+                                      {"service.instance.id", "instance2"},
+                                      {"service.name", "name1"},
+                                  },
+                              .num = 3,
+                          },
+                      .metric =
+                          {
+                              .ptr =
+                                  &(metric_t){
+                                      .value.counter = 23,
+                                  },
+                              .num = 1,
+                          },
+                  },
+              },
+          .fams_num = 1,
+          // clang-format off
+          .want =
+            "# HELP target_info Target metadata\n"
+           "# TYPE target_info gauge\n"
+           "target_info{job=\"name1\",instance=\"instance1\",host_name=\"example.org\"} 1\n"
+           "target_info{job=\"name1\",instance=\"instance2\",host_name=\"example.net\"} 1\n"
+           "\n"
+           "# HELP unit_test_total\n"
+           "# TYPE unit_test_total counter\n"
+           "unit_test_total{job=\"name1\",instance=\"instance1\"} 42\n"
+           "unit_test_total{job=\"name1\",instance=\"instance2\"} 23\n"
+           "\n"
+           "# collectd/write_prometheus " PACKAGE_VERSION " at example.com\n",
+          // clang-format on
+      },
+  };
+
+  for (size_t i = 0; i < STATIC_ARRAY_SIZE(cases); i++) {
+    printf("# Case %zu: %s\n", i, cases[i].name);
+
+    CHECK_ZERO(alloc_metrics());
+
+    for (size_t j = 0; j < cases[i].fams_num; j++) {
+      CHECK_ZERO(prom_write(cases[i].fams + j, NULL));
+    }
+
+    strbuf_t got = STRBUF_CREATE;
+    format_text(&got);
+
+    EXPECT_EQ_STR(cases[i].want, got.ptr);
+
+    STRBUF_DESTROY(got);
+    free_metrics();
+  }
+
+  return 0;
+}
+
 int main(void) {
   RUN_TEST(format_label_name);
   RUN_TEST(format_metric_family_name);
   RUN_TEST(format_metric_family);
   RUN_TEST(target_info);
+  RUN_TEST(end_to_end);
 
   END_TEST;
 }