}
}
-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);
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) {
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;
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);
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;
}