summaryrefslogtreecommitdiffstats
path: root/src/fluent-bit/lib/cmetrics/tests
diff options
context:
space:
mode:
Diffstat (limited to 'src/fluent-bit/lib/cmetrics/tests')
-rw-r--r--src/fluent-bit/lib/cmetrics/tests/CMakeLists.txt51
-rw-r--r--src/fluent-bit/lib/cmetrics/tests/atomic_operations.c120
-rw-r--r--src/fluent-bit/lib/cmetrics/tests/basic.c130
-rw-r--r--src/fluent-bit/lib/cmetrics/tests/cat.c238
-rw-r--r--src/fluent-bit/lib/cmetrics/tests/cmt_tests.h35
-rw-r--r--src/fluent-bit/lib/cmetrics/tests/cmt_tests_config.h.in25
-rw-r--r--src/fluent-bit/lib/cmetrics/tests/counter.c264
-rw-r--r--src/fluent-bit/lib/cmetrics/tests/data/histogram_different_label_count.txt14
-rw-r--r--src/fluent-bit/lib/cmetrics/tests/data/issue_6534.txt165
-rw-r--r--src/fluent-bit/lib/cmetrics/tests/data/issue_71.txt11
-rw-r--r--src/fluent-bit/lib/cmetrics/tests/data/issue_fluent_bit_5541.txt19
-rw-r--r--src/fluent-bit/lib/cmetrics/tests/data/issue_fluent_bit_5894.txt141
-rw-r--r--src/fluent-bit/lib/cmetrics/tests/data/issue_fluent_bit_6021.txt221
-rw-r--r--src/fluent-bit/lib/cmetrics/tests/data/pr_168.txt22
-rw-r--r--src/fluent-bit/lib/cmetrics/tests/decoding.c184
-rw-r--r--src/fluent-bit/lib/cmetrics/tests/encode_output.c67
-rw-r--r--src/fluent-bit/lib/cmetrics/tests/encode_output.h28
-rw-r--r--src/fluent-bit/lib/cmetrics/tests/encoding.c1056
-rw-r--r--src/fluent-bit/lib/cmetrics/tests/gauge.c174
-rw-r--r--src/fluent-bit/lib/cmetrics/tests/histogram.c212
-rw-r--r--src/fluent-bit/lib/cmetrics/tests/issues.c95
-rw-r--r--src/fluent-bit/lib/cmetrics/tests/lib/acutest/acutest.h1794
-rw-r--r--src/fluent-bit/lib/cmetrics/tests/null_label.c167
-rw-r--r--src/fluent-bit/lib/cmetrics/tests/prometheus_lexer.c218
-rw-r--r--src/fluent-bit/lib/cmetrics/tests/prometheus_parser.c1701
-rw-r--r--src/fluent-bit/lib/cmetrics/tests/summary.c172
-rw-r--r--src/fluent-bit/lib/cmetrics/tests/untyped.c122
-rw-r--r--src/fluent-bit/lib/cmetrics/tests/util.c68
28 files changed, 7514 insertions, 0 deletions
diff --git a/src/fluent-bit/lib/cmetrics/tests/CMakeLists.txt b/src/fluent-bit/lib/cmetrics/tests/CMakeLists.txt
new file mode 100644
index 000000000..d7ff19b35
--- /dev/null
+++ b/src/fluent-bit/lib/cmetrics/tests/CMakeLists.txt
@@ -0,0 +1,51 @@
+set(UNIT_TESTS_FILES
+ basic.c
+ gauge.c
+ counter.c
+ summary.c
+ histogram.c
+ untyped.c
+ atomic_operations.c
+ encoding.c
+ decoding.c
+ cat.c
+ issues.c
+ null_label.c
+ )
+
+if (CMT_BUILD_PROMETHEUS_DECODER)
+ set(UNIT_TESTS_FILES
+ ${UNIT_TESTS_FILES}
+ prometheus_lexer.c
+ prometheus_parser.c)
+endif()
+
+set(CMT_TESTS_DATA_PATH "${CMAKE_CURRENT_SOURCE_DIR}/data")
+configure_file(
+ "${CMAKE_CURRENT_SOURCE_DIR}/cmt_tests_config.h.in"
+ "${CMAKE_CURRENT_BINARY_DIR}/cmt_tests_config.h"
+ )
+
+# Prepare list of unit tests
+foreach(source_file ${UNIT_TESTS_FILES})
+ get_filename_component(source_file_we ${source_file} NAME_WE)
+ set(source_file_we cmt-test-${source_file_we})
+
+ add_executable(
+ ${source_file_we}
+ ${source_file}
+ util.c
+ encode_output.c
+ )
+
+ target_link_libraries(${source_file_we} cmetrics-static cfl-static fluent-otel-proto)
+
+if(NOT CMT_SYSTEM_WINDOWS)
+ target_link_libraries(${source_file_we} pthread)
+endif()
+
+ add_test(NAME ${source_file_we}
+ COMMAND ${CMAKE_BINARY_DIR}/tests/${source_file_we}
+ WORKING_DIRECTORY ${CMAKE_HOME_DIRECTORY}/tests)
+ set_tests_properties(${source_file_we} PROPERTIES LABELS "internal")
+endforeach()
diff --git a/src/fluent-bit/lib/cmetrics/tests/atomic_operations.c b/src/fluent-bit/lib/cmetrics/tests/atomic_operations.c
new file mode 100644
index 000000000..6734a59cf
--- /dev/null
+++ b/src/fluent-bit/lib/cmetrics/tests/atomic_operations.c
@@ -0,0 +1,120 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* CMetrics
+ * ========
+ * Copyright 2021-2022 The CMetrics Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cmetrics/cmetrics.h>
+#include <cmetrics/cmt_atomic.h>
+
+#if defined (_WIN32) || defined (_WIN64)
+#include <windows.h>
+#else
+#include <pthread.h>
+#endif
+
+#include "cmt_tests.h"
+
+#define THREAD_COUNT 100
+#define CYCLE_COUNT 10000
+#define EXPECTED_VALUE (THREAD_COUNT * CYCLE_COUNT)
+
+uint64_t global_counter;
+
+static inline void add_through_compare_exchange(uint64_t val)
+{
+ uint64_t old;
+ uint64_t new;
+ int result;
+
+ do {
+ old = global_counter;
+ new = old + val;
+
+ result = cmt_atomic_compare_exchange(&global_counter, old, new);
+ }
+ while(0 == result);
+}
+
+void *worker_thread_add_through_compare_exchange(void *ptr)
+{
+ int local_counter;
+
+ for (local_counter = 0 ; local_counter < CYCLE_COUNT ; local_counter++) {
+ add_through_compare_exchange(1);
+ }
+
+ return NULL;
+}
+
+#if defined (_WIN32) || defined (_WIN64)
+
+void test_atomic_operations()
+{
+ HANDLE threads[THREAD_COUNT];
+ DWORD thread_ids[THREAD_COUNT];
+ int thread_index;
+ DWORD result;
+
+ cmt_initialize();
+
+ global_counter = 0;
+
+ for(thread_index = 0 ; thread_index < THREAD_COUNT ; thread_index++)
+ {
+ threads[thread_index] = CreateThread(NULL, 0,
+ (LPTHREAD_START_ROUTINE) worker_thread_add_through_compare_exchange,
+ NULL, 0, &thread_ids[thread_index]);
+ }
+
+ for(thread_index = 0 ; thread_index < THREAD_COUNT ; thread_index++)
+ {
+ result = WaitForSingleObject(threads[thread_index], INFINITE);
+ }
+
+ TEST_CHECK(global_counter == EXPECTED_VALUE);
+}
+
+#else
+
+void test_atomic_operations()
+{
+ pthread_t threads[THREAD_COUNT];
+ int thread_index;
+
+ cmt_initialize();
+
+ global_counter = 0;
+
+ for(thread_index = 0 ; thread_index < THREAD_COUNT ; thread_index++)
+ {
+ pthread_create(&threads[thread_index], NULL,
+ worker_thread_add_through_compare_exchange, NULL);
+ }
+
+ for(thread_index = 0 ; thread_index < THREAD_COUNT ; thread_index++)
+ {
+ pthread_join(threads[thread_index], NULL);
+ }
+
+ TEST_CHECK(global_counter == EXPECTED_VALUE);
+}
+#endif
+
+TEST_LIST = {
+ {"atomic_operations", test_atomic_operations},
+ { 0 }
+};
diff --git a/src/fluent-bit/lib/cmetrics/tests/basic.c b/src/fluent-bit/lib/cmetrics/tests/basic.c
new file mode 100644
index 000000000..459dc65fe
--- /dev/null
+++ b/src/fluent-bit/lib/cmetrics/tests/basic.c
@@ -0,0 +1,130 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* CMetrics
+ * ========
+ * Copyright 2021-2022 The CMetrics Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cmetrics/cmetrics.h>
+#include <cmetrics/cmt_counter.h>
+#include <cmetrics/cmt_encode_msgpack.h>
+#include <cmetrics/cmt_decode_msgpack.h>
+#include <cmetrics/cmt_encode_text.h>
+
+#include "cmt_tests.h"
+
+static struct cmt *sample_data()
+{
+ double val;
+ uint64_t ts;
+ struct cmt *cmt;
+ struct cmt_counter *c1;
+ struct cmt_counter *c2;
+
+ cmt = cmt_create();
+
+ c1 = cmt_counter_create(cmt, "kubernetes", "network", "load", "Network load",
+ 2, (char *[]) {"hostname", "app"});
+
+ ts = 0;
+
+ cmt_counter_get_val(c1, 0, NULL, &val);
+ cmt_counter_inc(c1, ts, 0, NULL);
+ cmt_counter_add(c1, ts, 2, 0, NULL);
+ cmt_counter_get_val(c1, 0, NULL, &val);
+
+ cmt_counter_inc(c1, ts, 2, (char *[]) {"localhost", "cmetrics"});
+ cmt_counter_get_val(c1, 2, (char *[]) {"localhost", "cmetrics"}, &val);
+ cmt_counter_add(c1, ts, 10.55, 2, (char *[]) {"localhost", "test"});
+ cmt_counter_get_val(c1, 2, (char *[]) {"localhost", "test"}, &val);
+ cmt_counter_set(c1, ts, 12.15, 2, (char *[]) {"localhost", "test"});
+ cmt_counter_set(c1, ts, 1, 2, (char *[]) {"localhost", "test"});
+
+
+ c2 = cmt_counter_create(cmt, "kubernetes", "network", "cpu", "CPU load",
+ 2, (char *[]) {"hostname", "app"});
+
+ ts = 0;
+
+ cmt_counter_get_val(c2, 0, NULL, &val);
+ cmt_counter_inc(c2, ts, 0, NULL);
+ cmt_counter_add(c2, ts, 2, 0, NULL);
+ cmt_counter_get_val(c2, 0, NULL, &val);
+
+ cmt_counter_inc(c2, ts, 2, (char *[]) {"localhost", "cmetrics"});
+ cmt_counter_get_val(c2, 2, (char *[]) {"localhost", "cmetrics"}, &val);
+ cmt_counter_add(c2, ts, 10.55, 2, (char *[]) {"localhost", "test"});
+ cmt_counter_get_val(c2, 2, (char *[]) {"localhost", "test"}, &val);
+ cmt_counter_set(c2, ts, 12.15, 2, (char *[]) {"localhost", "test"});
+ cmt_counter_set(c2, ts, 1, 2, (char *[]) {"localhost", "test"});
+
+ return cmt;
+}
+
+void test_basic()
+{
+ int ret;
+ int error;
+ size_t off = 0;
+ cfl_sds_t text1;
+ cfl_sds_t text2;
+ char *mp_buf;
+ size_t mp_size;
+ struct cmt *cmt1;
+ struct cmt *cmt2;
+
+ cmt1 = sample_data();
+ TEST_CHECK(cmt1 != NULL);
+
+ /* encode to text */
+ text1 = cmt_encode_text_create(cmt1);
+ TEST_CHECK(text1 != NULL);
+
+ /* encode to msgpack */
+ ret = cmt_encode_msgpack_create(cmt1, &mp_buf, &mp_size);
+ TEST_CHECK(ret == 0);
+
+ /* decode msgpack into cmt2 */
+ ret = cmt_decode_msgpack_create(&cmt2, mp_buf, mp_size, &off);
+ TEST_CHECK(ret == 0);
+
+ /* encode cmt2 to text */
+ text2 = cmt_encode_text_create(cmt2);
+ TEST_CHECK(text2 != NULL);
+
+ /* compate both texts */
+ error = 0;
+ if ((cfl_sds_len(text1) != cfl_sds_len(text2)) ||
+ strcmp(text1, text2) != 0) {
+
+ printf("\n");
+ printf("====== EXPECTED OUTPUT =====\n%s", text1);
+ printf("\n\n");
+ printf("====== RECEIVED OUTPUT =====\n%s\n", text2);
+ error = 1;
+ }
+ TEST_CHECK(error == 0);
+
+ cmt_encode_msgpack_destroy(mp_buf);
+ cmt_encode_text_destroy(text1);
+ cmt_encode_text_destroy(text2);
+ cmt_destroy(cmt1);
+ cmt_destroy(cmt2);
+}
+
+TEST_LIST = {
+ {"basic", test_basic},
+ { 0 }
+};
diff --git a/src/fluent-bit/lib/cmetrics/tests/cat.c b/src/fluent-bit/lib/cmetrics/tests/cat.c
new file mode 100644
index 000000000..c1bf982e9
--- /dev/null
+++ b/src/fluent-bit/lib/cmetrics/tests/cat.c
@@ -0,0 +1,238 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* CMetrics
+ * ========
+ * Copyright 2021-2022 The CMetrics Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cmetrics/cmetrics.h>
+#include <cmetrics/cmt_counter.h>
+#include <cmetrics/cmt_gauge.h>
+#include <cmetrics/cmt_untyped.h>
+#include <cmetrics/cmt_histogram.h>
+#include <cmetrics/cmt_summary.h>
+#include <cmetrics/cmt_encode_text.h>
+#include <cmetrics/cmt_cat.h>
+
+#include "cmt_tests.h"
+
+/* values to observe in a histogram */
+double hist_observe_values[10] = {
+ 0.0 , 1.02, 2.04, 3.06,
+ 4.08, 5.10, 6.12, 7.14,
+ 8.16, 9.18
+ };
+
+/*
+ * histogram bucket values: the values computed in the buckets,
+ * all of them are uint64_t.
+ *
+ * Note that on all examples we use the default buckets values, created manually
+ * and through the API:
+ *
+ * - 11 bucket values
+ * - 1 +Inf bucket value
+ */
+uint64_t hist_buckets_values[12] = {1, 1, 1, 1, 1, 1, 1, 1,
+ 3, 5, 10, 10};
+/* histogram _count value */
+uint64_t hist_count = 10;
+
+/* histogram _sum value */
+double hist_sum = 45.9;
+
+void test_cat()
+{
+ int i;
+ int ret;
+ uint64_t val;
+ uint64_t ts;
+ cfl_sds_t text;
+ double sum;
+ uint64_t count;
+ double q[6];
+ double r[6];
+ struct cmt *cmt1;
+ struct cmt *cmt2;
+ struct cmt *cmt3;
+ struct cmt *cmt4;
+ struct cmt *cmt5;
+ struct cmt_counter *c;
+ struct cmt_gauge *g;
+ struct cmt_untyped *u;
+ struct cmt_histogram *h;
+ struct cmt_histogram_buckets *buckets;
+ struct cmt_summary *s;
+
+ /* cmetrics 1 */
+ cmt1 = cmt_create();
+ TEST_CHECK(cmt1 != NULL);
+
+ c = cmt_counter_create(cmt1, "cmetrics", "test", "cat_counter", "first counter",
+ 2, (char *[]) {"label1", "label2"});
+ TEST_CHECK(c != NULL);
+
+ g = cmt_gauge_create(cmt1, "cmetrics", "test", "cat_gauge", "first gauge",
+ 2, (char *[]) {"label3", "label4"});
+ TEST_CHECK(g != NULL);
+
+ u = cmt_untyped_create(cmt1, "cmetrics", "test", "cat_untyped", "first untyped",
+ 2, (char *[]) {"label5", "label6"});
+ TEST_CHECK(u != NULL);
+
+
+ ts = cfl_time_now();
+ cmt_counter_set(c, ts, 1.1, 2, (char *[]) {"aaa", "bbb"});
+
+ ts = cfl_time_now();
+ cmt_gauge_set(g, ts, 1.2, 2, (char *[]) {"yyy", "xxx"});
+
+ ts = cfl_time_now();
+ cmt_untyped_set(u, ts, 1.3, 2, (char *[]) {"qwe", "asd"});
+
+ /* cmetrics 2 */
+ cmt2 = cmt_create();
+ TEST_CHECK(cmt2 != NULL);
+
+ c = cmt_counter_create(cmt2, "cmetrics", "test", "cat_counter", "second counter",
+ 2, (char *[]) {"label1", "label2"});
+ TEST_CHECK(c != NULL);
+
+ g = cmt_gauge_create(cmt1, "cmetrics", "test", "cat_gauge", "first gauge",
+ 2, (char *[]) {"label3", "label4"});
+ TEST_CHECK(g != NULL);
+
+ ts = cfl_time_now();
+ cmt_counter_set(c, ts, 2.1, 2, (char *[]) {"ccc", "ddd"});
+
+ /* no labels */
+ cmt_counter_set(c, ts, 5, 0, NULL);
+
+ ts = cfl_time_now();
+ cmt_gauge_add(g, ts, 10, 2, (char *[]) {"tyu", "iop"});
+
+ /*
+ * CAT
+ * ---
+ */
+
+ cmt3 = cmt_create();
+ TEST_CHECK(cmt3 != NULL);
+
+ ret = cmt_cat(cmt3, cmt1);
+ TEST_CHECK(ret == 0);
+
+ ret = cmt_cat(cmt3, cmt2);
+ TEST_CHECK(ret == 0);
+
+ /* Create buckets */
+ buckets = cmt_histogram_buckets_create(11,
+ 0.005, 0.01, 0.025, 0.05,
+ 0.1, 0.25, 0.5, 1.0, 2.5,
+ 5.0, 10.0);
+ TEST_CHECK(buckets != NULL);
+
+ cmt4 = cmt_create();
+ TEST_CHECK(cmt4 != NULL);
+
+ /* Create a histogram metric type */
+ h = cmt_histogram_create(cmt4,
+ "k8s", "network", "load", "Network load",
+ buckets,
+ 1, (char *[]) {"my_label"});
+ TEST_CHECK(h != NULL);
+
+ ts = cfl_time_now();
+ for (i = 0; i < sizeof(hist_observe_values)/(sizeof(double)); i++) {
+ val = hist_observe_values[i];
+ cmt_histogram_observe(h, ts, val, 1, (char *[]) {"my_label"});
+ }
+
+ ret = cmt_cat(cmt4, cmt3);
+ TEST_CHECK(ret == 0);
+
+ cmt5 = cmt_create();
+ TEST_CHECK(cmt5 != NULL);
+
+ ts = cfl_time_now();
+
+ /* set quantiles */
+ q[0] = 0.1;
+ q[1] = 0.2;
+ q[2] = 0.3;
+ q[3] = 0.4;
+ q[4] = 0.5;
+ q[5] = 1.0;
+
+ r[0] = 1;
+ r[1] = 2;
+ r[2] = 3;
+ r[3] = 4;
+ r[4] = 5;
+ r[5] = 6;
+
+ /* Create a gauge metric type */
+ s = cmt_summary_create(cmt5,
+ "spring", "kafka_listener", "seconds", "Kafka Listener Timer",
+ 6, q,
+ 3, (char *[]) {"exception", "name", "result"});
+ TEST_CHECK(s != NULL);
+
+ /* no quantiles, labels */
+ sum = 0.0;
+ count = 1;
+
+ cmt_summary_set_default(s, ts, NULL, sum, count,
+ 3, (char *[]) {"ListenerExecutionFailedException",
+ "org.springframework.kafka.KafkaListenerEndpointContainer#0-0",
+ "failure"});
+
+ /* no quantiles, labels */
+ sum = 0.1;
+ count = 2;
+ cmt_summary_set_default(s, ts, NULL, sum, count,
+ 3, (char *[]) {"none",
+ "org.springframework.kafka.KafkaListenerEndpointContainer#0-0",
+ "success"});
+
+ /* quantiles, labels */
+ sum = 0.2;
+ count = 3;
+ cmt_summary_set_default(s, ts, r, sum, count,
+ 3, (char *[]) {"extra test",
+ "org.springframework.kafka.KafkaListenerEndpointContainer#0-0",
+ "success"});
+
+ ret = cmt_cat(cmt5, cmt4);
+ TEST_CHECK(ret == 0);
+
+ /* check output */
+ text = cmt_encode_text_create(cmt5);
+ printf("====>\n%s\n", text);
+
+ cmt_encode_text_destroy(text);
+
+ /* destroy contexts */
+ cmt_destroy(cmt1);
+ cmt_destroy(cmt2);
+ cmt_destroy(cmt3);
+ cmt_destroy(cmt4);
+ cmt_destroy(cmt5);
+}
+
+TEST_LIST = {
+ {"cat", test_cat},
+ { 0 }
+};
diff --git a/src/fluent-bit/lib/cmetrics/tests/cmt_tests.h b/src/fluent-bit/lib/cmetrics/tests/cmt_tests.h
new file mode 100644
index 000000000..dd8dad801
--- /dev/null
+++ b/src/fluent-bit/lib/cmetrics/tests/cmt_tests.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* CMetrics
+ * ========
+ * Copyright 2021-2022 The CMetrics Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CMT_TESTS_H
+#define CMT_TESTS_H
+
+#include "lib/acutest/acutest.h"
+
+#define MSGPACK_STABILITY_TEST_ITERATION_COUNT 1000
+#define MSGPACK_PARTIAL_PROCESSING_ELEMENT_COUNT 20
+
+#include "tests/cmt_tests_config.h"
+#include "encode_output.h"
+
+#include <cmetrics/cmetrics.h>
+
+cfl_sds_t read_file(const char *path);
+
+#endif
diff --git a/src/fluent-bit/lib/cmetrics/tests/cmt_tests_config.h.in b/src/fluent-bit/lib/cmetrics/tests/cmt_tests_config.h.in
new file mode 100644
index 000000000..fcc5a75b6
--- /dev/null
+++ b/src/fluent-bit/lib/cmetrics/tests/cmt_tests_config.h.in
@@ -0,0 +1,25 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* CMetrics
+ * ========
+ * Copyright 2021-2022 The CMetrics Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CMT_TESTS_CONFIG_H
+#define CMT_TESTS_CONFIG_H
+
+#define CMT_TESTS_DATA_PATH "@CMT_TESTS_DATA_PATH@"
+
+#endif
diff --git a/src/fluent-bit/lib/cmetrics/tests/counter.c b/src/fluent-bit/lib/cmetrics/tests/counter.c
new file mode 100644
index 000000000..cfe83c6cf
--- /dev/null
+++ b/src/fluent-bit/lib/cmetrics/tests/counter.c
@@ -0,0 +1,264 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* CMetrics
+ * ========
+ * Copyright 2021-2022 The CMetrics Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cmetrics/cmetrics.h>
+#include <cmetrics/cmt_counter.h>
+#include <cmetrics/cmt_encode_msgpack.h>
+#include <cmetrics/cmt_decode_msgpack.h>
+#include <cmetrics/cmt_encode_prometheus.h>
+#include <cmetrics/cmt_encode_text.h>
+
+#include "cmt_tests.h"
+
+static struct cmt *generate_encoder_test_data()
+{
+ double val;
+ uint64_t ts;
+ struct cmt *cmt;
+ struct cmt_counter *c;
+
+ printf("version: %s", cmt_version());
+ cmt = cmt_create();
+
+ c = cmt_counter_create(cmt, "kubernetes", "network", "load", "Network load",
+ 2, (char *[]) {"hostname", "app"});
+
+ ts = cfl_time_now();
+
+ cmt_counter_get_val(c, 0, NULL, &val);
+ cmt_counter_inc(c, ts, 0, NULL);
+ cmt_counter_add(c, ts, 2, 0, NULL);
+ cmt_counter_get_val(c, 0, NULL, &val);
+
+ cmt_counter_inc(c, ts, 2, (char *[]) {"localhost", "cmetrics"});
+ cmt_counter_get_val(c, 2, (char *[]) {"localhost", "cmetrics"}, &val);
+ cmt_counter_add(c, ts, 10.55, 2, (char *[]) {"localhost", "test"});
+ cmt_counter_get_val(c, 2, (char *[]) {"localhost", "test"}, &val);
+ cmt_counter_set(c, ts, 12.15, 2, (char *[]) {"localhost", "test"});
+ cmt_counter_set(c, ts, 1, 2, (char *[]) {"localhost", "test"});
+
+ return cmt;
+}
+
+void test_msgpack()
+{
+ struct cmt *cmt = NULL;
+ struct cmt *cmt2 = NULL;
+ int result = 0;
+ size_t offset = 0;
+ char *msgpack_buffer_a = NULL;
+ char *msgpack_buffer_b = NULL;
+ size_t msgpack_buffer_size_a = 0;
+ size_t msgpack_buffer_size_b = 0;
+
+ cmt_initialize();
+
+ cmt = generate_encoder_test_data();
+ TEST_CHECK(NULL != cmt);
+
+ result = cmt_encode_msgpack_create(cmt, &msgpack_buffer_a, &msgpack_buffer_size_a);
+ TEST_CHECK(0 == result);
+
+ result = cmt_decode_msgpack_create(&cmt2, msgpack_buffer_a, msgpack_buffer_size_a,
+ &offset);
+ TEST_CHECK(0 == result);
+
+ result = cmt_encode_msgpack_create(cmt, &msgpack_buffer_b, &msgpack_buffer_size_b);
+ TEST_CHECK(0 == result);
+
+ TEST_CHECK(msgpack_buffer_size_a == msgpack_buffer_size_b);
+
+ result = memcmp(msgpack_buffer_a, msgpack_buffer_b, msgpack_buffer_size_a);
+
+ cmt_destroy(cmt);
+ cmt_decode_msgpack_destroy(cmt2);
+ cmt_encode_msgpack_destroy(msgpack_buffer_a);
+ cmt_encode_msgpack_destroy(msgpack_buffer_b);
+}
+
+void test_prometheus()
+{
+ struct cmt *cmt = NULL;
+ cfl_sds_t prom = NULL;
+
+ cmt_initialize();
+
+ cmt = generate_encoder_test_data();
+ TEST_CHECK(NULL != cmt);
+
+ prom = cmt_encode_prometheus_create(cmt, CMT_TRUE);
+ TEST_CHECK(NULL != prom);
+ printf("%s\n", prom);
+
+ cmt_destroy(cmt);
+ cmt_encode_prometheus_destroy(prom);
+}
+
+void test_text()
+{
+ struct cmt *cmt = NULL;
+ cfl_sds_t text = NULL;
+
+ cmt_initialize();
+
+ cmt = generate_encoder_test_data();
+ TEST_CHECK(cmt != NULL);
+
+ text = cmt_encode_text_create(cmt);
+ TEST_CHECK(text != NULL);
+
+ cmt_destroy(cmt);
+ cmt_encode_text_destroy(text);
+}
+
+
+void test_counter()
+{
+ int ret;
+ double val = 1;
+ uint64_t ts;
+ struct cmt *cmt;
+ struct cmt_counter *c;
+
+ cmt_initialize();
+
+ cmt = cmt_create();
+ TEST_CHECK(cmt != NULL);
+
+ /* Create a counter metric type */
+ c = cmt_counter_create(cmt, "kubernetes", "network", "load", "Network load",
+ 0, NULL);
+ TEST_CHECK(c != NULL);
+
+ /* Timestamp */
+ ts = cfl_time_now();
+
+ /* Default value */
+ ret = cmt_counter_get_val(c, 0, NULL, &val);
+ TEST_CHECK(ret == 0);
+ TEST_CHECK(val == 0.0);
+
+ /* Increment by one */
+ cmt_counter_inc(c, ts, 0, NULL);
+ ret = cmt_counter_get_val(c, 0, NULL, &val);
+ TEST_CHECK(val == 1.0);
+
+ /* Add two */
+ cmt_counter_add(c, ts, 2, 0, NULL);
+ ret = cmt_counter_get_val(c, 0, NULL, &val);
+
+ TEST_CHECK(ret == 0);
+ TEST_CHECK(val == 3.0);
+
+ cmt_destroy(cmt);
+}
+
+void test_labels()
+{
+ int ret;
+ double val;
+ uint64_t ts;
+ struct cmt *cmt;
+ struct cmt_counter *c;
+
+ cmt_initialize();
+
+ cmt = cmt_create();
+ TEST_CHECK(cmt != NULL);
+
+ /* Create a counter metric type */
+ c = cmt_counter_create(cmt, "kubernetes", "network", "load", "Network load",
+ 2, (char *[]) {"hostname", "app"});
+ TEST_CHECK(c != NULL);
+
+ /* Timestamp */
+ ts = cfl_time_now();
+
+ /*
+ * Test 1: hash zero (no labels)
+ * -----------------------------
+ */
+
+ /*
+ * Default value: this call should fail since the metric has not been
+ * initialized.
+ */
+ ret = cmt_counter_get_val(c, 0, NULL, &val);
+ TEST_CHECK(ret == -1);
+
+ /* Increment hash zero by 1 */
+ ret = cmt_counter_inc(c, ts, 0, NULL);
+ TEST_CHECK(ret == 0);
+
+ /* validate value */
+ ret = cmt_counter_get_val(c, 0, NULL, &val);
+ TEST_CHECK(ret == 0);
+ TEST_CHECK(val == 1.0);
+
+ /* Add two */
+ ret = cmt_counter_add(c, ts, 2, 0, NULL);
+ TEST_CHECK(ret == 0);
+
+ /* Check that hash zero val is 3.0 */
+ ret = cmt_counter_get_val(c, 0, NULL, &val);
+ TEST_CHECK(ret == 0);
+ TEST_CHECK(val == 3.0);
+
+ /*
+ * Test 2: custom labels
+ * ---------------------
+ */
+
+ /* Increment custom metric */
+ ret = cmt_counter_inc(c, ts, 2, (char *[]) {"localhost", "cmetrics"});
+ TEST_CHECK(ret == 0);
+
+ /* Check val = 1 */
+ ret = cmt_counter_get_val(c, 2, (char *[]) {"localhost", "cmetrics"}, &val);
+ TEST_CHECK(ret == 0);
+ TEST_CHECK(val == 1.000);
+
+ /* Add 10 to another metric using a different second label */
+ ret = cmt_counter_add(c, ts, 10.55, 2, (char *[]) {"localhost", "test"});
+ TEST_CHECK(ret == 0);
+
+ /* Validate the value */
+ ret = cmt_counter_get_val(c, 2, (char *[]) {"localhost", "test"}, &val);
+ TEST_CHECK(ret == 0);
+ TEST_CHECK(val == 10.55);
+
+ /* Valid counter set */
+ ret = cmt_counter_set(c, ts, 12.15, 2, (char *[]) {"localhost", "test"});
+ TEST_CHECK(ret == 0);
+
+ /* Invalid counter set */
+ ret = cmt_counter_set(c, ts, 1, 2, (char *[]) {"localhost", "test"});
+ TEST_CHECK(ret == -1);
+
+ cmt_destroy(cmt);
+}
+
+TEST_LIST = {
+ {"basic", test_counter},
+ {"labels", test_labels},
+ {"msgpack", test_msgpack},
+ {"prometheus", test_prometheus},
+ {"text", test_text},
+ { 0 }
+};
diff --git a/src/fluent-bit/lib/cmetrics/tests/data/histogram_different_label_count.txt b/src/fluent-bit/lib/cmetrics/tests/data/histogram_different_label_count.txt
new file mode 100644
index 000000000..3aab53682
--- /dev/null
+++ b/src/fluent-bit/lib/cmetrics/tests/data/histogram_different_label_count.txt
@@ -0,0 +1,14 @@
+# HELP k8s_network_load Network load
+# TYPE k8s_network_load histogram
+k8s_network_load_bucket{le="0.05"} 0 0
+k8s_network_load_bucket{le="5.0"} 1 0
+k8s_network_load_bucket{le="10.0"} 2 0
+k8s_network_load_bucket{le="+Inf"} 3 0
+k8s_network_load_sum 1013 0
+k8s_network_load_count 3 0
+k8s_network_load_bucket{le="0.05",my_label="my_val"} 0 0
+k8s_network_load_bucket{le="5.0",my_label="my_val"} 1 0
+k8s_network_load_bucket{le="10.0",my_label="my_val"} 2 0
+k8s_network_load_bucket{le="+Inf",my_label="my_val"} 3 0
+k8s_network_load_sum{my_label="my_val"} 1013 0
+k8s_network_load_count{my_label="my_val"} 3 0
diff --git a/src/fluent-bit/lib/cmetrics/tests/data/issue_6534.txt b/src/fluent-bit/lib/cmetrics/tests/data/issue_6534.txt
new file mode 100644
index 000000000..d19ebe99a
--- /dev/null
+++ b/src/fluent-bit/lib/cmetrics/tests/data/issue_6534.txt
@@ -0,0 +1,165 @@
+# HELP dotnet_threadpool_num_threads The number of active threads in the thread pool
+# TYPE dotnet_threadpool_num_threads gauge
+dotnet_threadpool_num_threads 6
+# HELP dotnet_jit_method_total Total number of methods compiled by the JIT compiler
+# TYPE dotnet_jit_method_total counter
+dotnet_jit_method_total 6476
+# HELP dotnet_gc_heap_size_bytes The current size of all heaps (only updated after a garbage collection)
+# TYPE dotnet_gc_heap_size_bytes gauge
+dotnet_gc_heap_size_bytes{gc_generation="0"} 24
+dotnet_gc_heap_size_bytes{gc_generation="loh"} 550280
+dotnet_gc_heap_size_bytes{gc_generation="2"} 2625704
+dotnet_gc_heap_size_bytes{gc_generation="1"} 226944
+# HELP dotnet_gc_collection_count_total Counts the number of garbage collections that have occurred, broken down by generation number and the reason for the collection.
+# TYPE dotnet_gc_collection_count_total counter
+dotnet_gc_collection_count_total{gc_generation="1",gc_reason="alloc_small"} 133
+dotnet_gc_collection_count_total{gc_generation="0",gc_reason="alloc_small"} 618
+dotnet_gc_collection_count_total{gc_generation="2",gc_reason="alloc_small"} 8
+# HELP dotnet_threadpool_timer_count The number of timers active
+# TYPE dotnet_threadpool_timer_count gauge
+dotnet_threadpool_timer_count 5
+# HELP dotnet_threadpool_queue_length Measures the queue length of the thread pool. Values greater than 0 indicate a backlog of work for the threadpool to process.
+# TYPE dotnet_threadpool_queue_length histogram
+dotnet_threadpool_queue_length_sum 5
+dotnet_threadpool_queue_length_count 321733
+dotnet_threadpool_queue_length_bucket{le="0"} 321728
+dotnet_threadpool_queue_length_bucket{le="1"} 321733
+dotnet_threadpool_queue_length_bucket{le="10"} 321733
+dotnet_threadpool_queue_length_bucket{le="100"} 321733
+dotnet_threadpool_queue_length_bucket{le="1000"} 321733
+dotnet_threadpool_queue_length_bucket{le="+Inf"} 321733
+# HELP dotnet_gc_cpu_ratio The percentage of process CPU time spent running garbage collections
+# TYPE dotnet_gc_cpu_ratio gauge
+dotnet_gc_cpu_ratio 0
+# HELP dotnet_collection_count_total GC collection count
+# TYPE dotnet_collection_count_total counter
+dotnet_collection_count_total{generation="0"} 759
+dotnet_collection_count_total{generation="2"} 8
+dotnet_collection_count_total{generation="1"} 141
+# HELP dotnet_threadpool_adjustments_total The total number of changes made to the size of the thread pool, labeled by the reason for change
+# TYPE dotnet_threadpool_adjustments_total counter
+dotnet_threadpool_adjustments_total{adjustment_reason="starvation"} 957
+dotnet_threadpool_adjustments_total{adjustment_reason="warmup"} 4
+dotnet_threadpool_adjustments_total{adjustment_reason="thread_timed_out"} 2409
+dotnet_threadpool_adjustments_total{adjustment_reason="climbing_move"} 93458
+# HELP process_open_handles Number of open handles
+# TYPE process_open_handles gauge
+process_open_handles 264
+# HELP dotnet_gc_pause_ratio The percentage of time the process spent paused for garbage collection
+# TYPE dotnet_gc_pause_ratio gauge
+dotnet_gc_pause_ratio 0
+# HELP process_cpu_seconds_total Total user and system CPU time spent in seconds.
+# TYPE process_cpu_seconds_total counter
+process_cpu_seconds_total 1450.85
+# HELP dotnet_gc_pause_seconds The amount of time execution was paused for garbage collection
+# TYPE dotnet_gc_pause_seconds histogram
+dotnet_gc_pause_seconds_sum 1.3192573999999997
+dotnet_gc_pause_seconds_count 759
+dotnet_gc_pause_seconds_bucket{le="0.001"} 7
+dotnet_gc_pause_seconds_bucket{le="0.01"} 747
+dotnet_gc_pause_seconds_bucket{le="0.05"} 759
+dotnet_gc_pause_seconds_bucket{le="0.1"} 759
+dotnet_gc_pause_seconds_bucket{le="0.5"} 759
+dotnet_gc_pause_seconds_bucket{le="1"} 759
+dotnet_gc_pause_seconds_bucket{le="10"} 759
+dotnet_gc_pause_seconds_bucket{le="+Inf"} 759
+# HELP dotnet_jit_il_bytes Total bytes of IL compiled by the JIT compiler
+# TYPE dotnet_jit_il_bytes gauge
+dotnet_jit_il_bytes 487850
+# HELP dotnet_gc_collection_seconds The amount of time spent running garbage collections
+# TYPE dotnet_gc_collection_seconds histogram
+dotnet_gc_collection_seconds_sum{gc_generation="1",gc_type="non_concurrent_gc"} 0.20421500000000006
+dotnet_gc_collection_seconds_count{gc_generation="1",gc_type="non_concurrent_gc"} 133
+dotnet_gc_collection_seconds_bucket{gc_generation="1",gc_type="non_concurrent_gc",le="0.001"} 3
+dotnet_gc_collection_seconds_bucket{gc_generation="1",gc_type="non_concurrent_gc",le="0.01"} 133
+dotnet_gc_collection_seconds_bucket{gc_generation="1",gc_type="non_concurrent_gc",le="0.05"} 133
+dotnet_gc_collection_seconds_bucket{gc_generation="1",gc_type="non_concurrent_gc",le="0.1"} 133
+dotnet_gc_collection_seconds_bucket{gc_generation="1",gc_type="non_concurrent_gc",le="0.5"} 133
+dotnet_gc_collection_seconds_bucket{gc_generation="1",gc_type="non_concurrent_gc",le="1"} 133
+dotnet_gc_collection_seconds_bucket{gc_generation="1",gc_type="non_concurrent_gc",le="10"} 133
+dotnet_gc_collection_seconds_bucket{gc_generation="1",gc_type="non_concurrent_gc",le="+Inf"} 133
+dotnet_gc_collection_seconds_sum 0
+dotnet_gc_collection_seconds_count 0
+dotnet_gc_collection_seconds_bucket{le="0.001"} 0
+dotnet_gc_collection_seconds_bucket{le="0.01"} 0
+dotnet_gc_collection_seconds_bucket{le="0.05"} 0
+dotnet_gc_collection_seconds_bucket{le="0.1"} 0
+dotnet_gc_collection_seconds_bucket{le="0.5"} 0
+dotnet_gc_collection_seconds_bucket{le="1"} 0
+dotnet_gc_collection_seconds_bucket{le="10"} 0
+dotnet_gc_collection_seconds_bucket{le="+Inf"} 0
+dotnet_gc_collection_seconds_sum{gc_generation="2",gc_type="non_concurrent_gc"} 0.09344780000000001
+dotnet_gc_collection_seconds_count{gc_generation="2",gc_type="non_concurrent_gc"} 8
+dotnet_gc_collection_seconds_bucket{gc_generation="2",gc_type="non_concurrent_gc",le="0.001"} 0
+dotnet_gc_collection_seconds_bucket{gc_generation="2",gc_type="non_concurrent_gc",le="0.01"} 0
+dotnet_gc_collection_seconds_bucket{gc_generation="2",gc_type="non_concurrent_gc",le="0.05"} 8
+dotnet_gc_collection_seconds_bucket{gc_generation="2",gc_type="non_concurrent_gc",le="0.1"} 8
+dotnet_gc_collection_seconds_bucket{gc_generation="2",gc_type="non_concurrent_gc",le="0.5"} 8
+dotnet_gc_collection_seconds_bucket{gc_generation="2",gc_type="non_concurrent_gc",le="1"} 8
+dotnet_gc_collection_seconds_bucket{gc_generation="2",gc_type="non_concurrent_gc",le="10"} 8
+dotnet_gc_collection_seconds_bucket{gc_generation="2",gc_type="non_concurrent_gc",le="+Inf"} 8
+dotnet_gc_collection_seconds_sum{gc_generation="0",gc_type="non_concurrent_gc"} 0.855451900000001
+dotnet_gc_collection_seconds_count{gc_generation="0",gc_type="non_concurrent_gc"} 618
+dotnet_gc_collection_seconds_bucket{gc_generation="0",gc_type="non_concurrent_gc",le="0.001"} 127
+dotnet_gc_collection_seconds_bucket{gc_generation="0",gc_type="non_concurrent_gc",le="0.01"} 617
+dotnet_gc_collection_seconds_bucket{gc_generation="0",gc_type="non_concurrent_gc",le="0.05"} 618
+dotnet_gc_collection_seconds_bucket{gc_generation="0",gc_type="non_concurrent_gc",le="0.1"} 618
+dotnet_gc_collection_seconds_bucket{gc_generation="0",gc_type="non_concurrent_gc",le="0.5"} 618
+dotnet_gc_collection_seconds_bucket{gc_generation="0",gc_type="non_concurrent_gc",le="1"} 618
+dotnet_gc_collection_seconds_bucket{gc_generation="0",gc_type="non_concurrent_gc",le="10"} 618
+dotnet_gc_collection_seconds_bucket{gc_generation="0",gc_type="non_concurrent_gc",le="+Inf"} 618
+# HELP dotnet_contention_total The number of locks contended
+# TYPE dotnet_contention_total counter
+dotnet_contention_total 6758
+# HELP dotnet_contention_seconds_total The total amount of time spent contending locks
+# TYPE dotnet_contention_seconds_total counter
+dotnet_contention_seconds_total 1.0246322000000074
+# HELP dotnet_gc_memory_total_available_bytes The upper limit on the amount of physical memory .NET can allocate to
+# TYPE dotnet_gc_memory_total_available_bytes gauge
+dotnet_gc_memory_total_available_bytes 805306368
+# HELP dotnet_build_info Build information about prometheus-net.DotNetRuntime and the environment
+# TYPE dotnet_build_info gauge
+dotnet_build_info{version="4.2.4.0",target_framework=".NETCoreApp,Version=v6.0",runtime_version=".NET 6.0.11",os_version="Linux 5.4.0-1094-azure #100~18.04.1-Ubuntu SMP Mon Oct 17 11:44:30 UTC 2022",process_architecture="X64",gc_mode="Workstation"} 1
+# HELP dotnet_internal_recycle_count prometheus-net.DotNetRuntime internal metric. Counts the number of times the underlying event listeners have been recycled
+# TYPE dotnet_internal_recycle_count counter
+dotnet_internal_recycle_count 3
+# HELP dotnet_gc_allocated_bytes_total The total number of bytes allocated on the managed heap
+# TYPE dotnet_gc_allocated_bytes_total counter
+dotnet_gc_allocated_bytes_total{gc_heap="soh"} 19853322336
+# HELP process_start_time_seconds Start time of the process since unix epoch in seconds.
+# TYPE process_start_time_seconds gauge
+process_start_time_seconds 1670526623.05
+# HELP process_cpu_count The number of processor cores available to this process.
+# TYPE process_cpu_count gauge
+process_cpu_count 1
+# HELP dotnet_gc_pinned_objects The number of pinned objects
+# TYPE dotnet_gc_pinned_objects gauge
+dotnet_gc_pinned_objects 0
+# HELP dotnet_total_memory_bytes Total known allocated memory
+# TYPE dotnet_total_memory_bytes gauge
+dotnet_total_memory_bytes 20979896
+# HELP process_virtual_memory_bytes Virtual memory size in bytes.
+# TYPE process_virtual_memory_bytes gauge
+process_virtual_memory_bytes 8562679808
+# HELP dotnet_threadpool_throughput_total The total number of work items that have finished execution in the thread pool
+# TYPE dotnet_threadpool_throughput_total counter
+dotnet_threadpool_throughput_total 3381388
+# HELP process_working_set_bytes Process working set
+# TYPE process_working_set_bytes gauge
+process_working_set_bytes 135118848
+# HELP process_num_threads Total number of threads
+# TYPE process_num_threads gauge
+process_num_threads 21
+# HELP dotnet_gc_finalization_queue_length The number of objects waiting to be finalized
+# TYPE dotnet_gc_finalization_queue_length gauge
+dotnet_gc_finalization_queue_length 15
+# HELP process_private_memory_bytes Process private memory size
+# TYPE process_private_memory_bytes gauge
+process_private_memory_bytes 247390208
+# HELP dotnet_exceptions_total Count of exceptions thrown, broken down by type
+# TYPE dotnet_exceptions_total counter
+dotnet_exceptions_total{type="System.Net.Http.HttpRequestException"} 792
+dotnet_exceptions_total{type="System.ObjectDisposedException"} 11977
+dotnet_exceptions_total{type="System.IO.DirectoryNotFoundException"} 14
+dotnet_exceptions_total{type="System.Net.Sockets.SocketException"} 258287
+dotnet_exceptions_total{type="Grpc.Core.RpcException"} 72
diff --git a/src/fluent-bit/lib/cmetrics/tests/data/issue_71.txt b/src/fluent-bit/lib/cmetrics/tests/data/issue_71.txt
new file mode 100644
index 000000000..2f16af55d
--- /dev/null
+++ b/src/fluent-bit/lib/cmetrics/tests/data/issue_71.txt
@@ -0,0 +1,11 @@
+# HELP node_power_supply_info info of /sys/class/power_supply/<power_supply>.
+# TYPE node_power_supply_info gauge
+node_power_supply_info{power_supply="AC",type="Mains"} 1
+node_power_supply_info{power_supply="ucsi-source-psy-USBC000:001",type="USB",usb_type="[C] PD PD_PPS"} 1
+node_power_supply_info{power_supply="ucsi-source-psy-USBC000:002",type="USB",usb_type="C [PD] PD_PPS"} 1
+node_power_supply_info{capacity_level="Normal",manufacturer="SMP",model_name="02DL005",power_supply="BAT0",serial_number="4195",status="Discharging",technology="Li-poly",type="Battery"} 1
+# HELP node_power_supply_online online value of /sys/class/power_supply/<power_supply>.
+# TYPE node_power_supply_online gauge
+node_power_supply_online{power_supply="AC"} 0
+node_power_supply_online{power_supply="ucsi-source-psy-USBC000:001"} 0
+node_power_supply_online{power_supply="ucsi-source-psy-USBC000:002"} 1
diff --git a/src/fluent-bit/lib/cmetrics/tests/data/issue_fluent_bit_5541.txt b/src/fluent-bit/lib/cmetrics/tests/data/issue_fluent_bit_5541.txt
new file mode 100644
index 000000000..b51a46c5d
--- /dev/null
+++ b/src/fluent-bit/lib/cmetrics/tests/data/issue_fluent_bit_5541.txt
@@ -0,0 +1,19 @@
+# HELP http_request_duration_seconds HTTP request latency (seconds)
+# TYPE http_request_duration_seconds histogram
+http_request_duration_seconds_bucket{le="0.005"} 2.0
+http_request_duration_seconds_bucket{le="0.01"} 2.0
+http_request_duration_seconds_bucket{le="0.025"} 2.0
+http_request_duration_seconds_bucket{le="0.05"} 2.0
+http_request_duration_seconds_bucket{le="0.075"} 2.0
+http_request_duration_seconds_bucket{le="0.1"} 2.0
+http_request_duration_seconds_bucket{le="0.25"} 2.0
+http_request_duration_seconds_bucket{le="0.5"} 2.0
+http_request_duration_seconds_bucket{le="0.75"} 2.0
+http_request_duration_seconds_bucket{le="1.0"} 2.0
+http_request_duration_seconds_bucket{le="2.5"} 2.0
+http_request_duration_seconds_bucket{le="5.0"} 2.0
+http_request_duration_seconds_bucket{le="7.5"} 2.0
+http_request_duration_seconds_bucket{le="10.0"} 2.0
+http_request_duration_seconds_bucket{le="+Inf"} 2.0
+http_request_duration_seconds_count 2.0
+http_request_duration_seconds_sum 0.0006913102697581053
diff --git a/src/fluent-bit/lib/cmetrics/tests/data/issue_fluent_bit_5894.txt b/src/fluent-bit/lib/cmetrics/tests/data/issue_fluent_bit_5894.txt
new file mode 100644
index 000000000..482494053
--- /dev/null
+++ b/src/fluent-bit/lib/cmetrics/tests/data/issue_fluent_bit_5894.txt
@@ -0,0 +1,141 @@
+# HELP process_start_time_seconds Start time of the process since unix epoch.
+# TYPE process_start_time_seconds gauge
+process_start_time_seconds 1.660594096832E9
+# HELP hikaricp_connections_timeout_total Connection timeout total count
+# TYPE hikaricp_connections_timeout_total counter
+hikaricp_connections_timeout_total{pool="mcadb",} 0.0
+# HELP spring_kafka_listener_seconds_max Kafka Listener Timer
+# TYPE spring_kafka_listener_seconds_max gauge
+spring_kafka_listener_seconds_max{exception="ListenerExecutionFailedException",name="org.springframework.kafka.KafkaListenerEndpointContainer#0-0",result="failure",} 0.0
+spring_kafka_listener_seconds_max{exception="none",name="org.springframework.kafka.KafkaListenerEndpointContainer#0-0",result="success",} 0.0
+# HELP spring_kafka_listener_seconds Kafka Listener Timer
+# TYPE spring_kafka_listener_seconds summary
+spring_kafka_listener_seconds_count{exception="ListenerExecutionFailedException",name="org.springframework.kafka.KafkaListenerEndpointContainer#0-0",result="failure",} 0.0
+spring_kafka_listener_seconds_sum{exception="ListenerExecutionFailedException",name="org.springframework.kafka.KafkaListenerEndpointContainer#0-0",result="failure",} 0.0
+spring_kafka_listener_seconds_count{exception="none",name="org.springframework.kafka.KafkaListenerEndpointContainer#0-0",result="success",} 0.0
+spring_kafka_listener_seconds_sum{exception="none",name="org.springframework.kafka.KafkaListenerEndpointContainer#0-0",result="success",} 0.0
+# HELP process_files_max_files The maximum file descriptor count
+# TYPE process_files_max_files gauge
+process_files_max_files 1048576.0
+# HELP hikaricp_connections_pending Pending threads
+# TYPE hikaricp_connections_pending gauge
+hikaricp_connections_pending{pool="mcadb",} 0.0
+# HELP jvm_memory_committed_bytes The amount of memory in bytes that is committed for the Java virtual machine to use
+# TYPE jvm_memory_committed_bytes gauge
+jvm_memory_committed_bytes{area="nonheap",id="CodeHeap 'profiled nmethods'",} 1.605632E7
+jvm_memory_committed_bytes{area="heap",id="G1 Survivor Space",} 2.097152E7
+jvm_memory_committed_bytes{area="heap",id="G1 Old Gen",} 2.32783872E8
+jvm_memory_committed_bytes{area="nonheap",id="Metaspace",} 1.03374848E8
+jvm_memory_committed_bytes{area="nonheap",id="CodeHeap 'non-nmethods'",} 4390912.0
+jvm_memory_committed_bytes{area="heap",id="G1 Eden Space",} 3.73293056E8
+jvm_memory_committed_bytes{area="nonheap",id="Compressed Class Space",} 1.3500416E7
+jvm_memory_committed_bytes{area="nonheap",id="CodeHeap 'non-profiled nmethods'",} 4521984.0
+# HELP process_files_open_files The open file descriptor count
+# TYPE process_files_open_files gauge
+process_files_open_files 290.0
+# HELP rabbitmq_consumed_total
+# TYPE rabbitmq_consumed_total counter
+rabbitmq_consumed_total{name="rabbit",} 0.0
+# HELP hikaricp_connections_usage_seconds Connection usage time
+# TYPE hikaricp_connections_usage_seconds summary
+hikaricp_connections_usage_seconds_count{pool="mcadb",} 0.0
+hikaricp_connections_usage_seconds_sum{pool="mcadb",} 0.0
+# HELP kafka_consumer_sync_time_max_seconds The max time taken for a group sync.
+# TYPE kafka_consumer_sync_time_max_seconds gauge
+kafka_consumer_sync_time_max_seconds{client_id="consumer-1",} NaN
+# HELP kafka_consumer_fetch_latency_avg_seconds The average time taken for a fetch request.
+# TYPE kafka_consumer_fetch_latency_avg_seconds gauge
+kafka_consumer_fetch_latency_avg_seconds{client_id="consumer-1",} NaN
+# HELP rabbitmq_channels
+# TYPE rabbitmq_channels gauge
+rabbitmq_channels{name="rabbit",} 0.0
+# HELP kafka_consumer_sync_rate_syncs The number of group syncs per second. Group synchronization is the second and last phase of the rebalance protocol. A large value indicates group instability.
+# TYPE kafka_consumer_sync_rate_syncs gauge
+kafka_consumer_sync_rate_syncs{client_id="consumer-1",} 0.0
+# HELP jvm_classes_loaded_classes The number of classes that are currently loaded in the Java virtual machine
+# TYPE jvm_classes_loaded_classes gauge
+jvm_classes_loaded_classes 17220.0
+# HELP jdbc_connections_min
+# TYPE jdbc_connections_min gauge
+jdbc_connections_min{name="dataSource",} 10.0
+# HELP kafka_consumer_fetch_throttle_time_avg_seconds The average throttle time. When quotas are enabled, the broker may delay fetch requests in order to throttle a consumer which has exceeded its limit. This metric indicates how throttling time has been added to fetch requests on average.
+# TYPE kafka_consumer_fetch_throttle_time_avg_seconds gauge
+kafka_consumer_fetch_throttle_time_avg_seconds{client_id="consumer-1",} NaN
+# HELP rabbitmq_failed_to_publish_total
+# TYPE rabbitmq_failed_to_publish_total counter
+rabbitmq_failed_to_publish_total{name="rabbit",} 0.0
+
+# HELP tomcat_sessions_active_max_sessions
+# TYPE tomcat_sessions_active_max_sessions gauge
+tomcat_sessions_active_max_sessions 0.0
+# HELP process_cpu_usage The "recent cpu usage" for the Java Virtual Machine process
+# TYPE process_cpu_usage gauge
+process_cpu_usage 7.079390305569602E-4
+# HELP jvm_buffer_total_capacity_bytes An estimate of the total capacity of the buffers in this pool
+# TYPE jvm_buffer_total_capacity_bytes gauge
+jvm_buffer_total_capacity_bytes{id="mapped",} 0.0
+jvm_buffer_total_capacity_bytes{id="direct",} 81920.0
+# HELP kafka_consumer_fetch_throttle_time_max_seconds The maximum throttle time.
+# TYPE kafka_consumer_fetch_throttle_time_max_seconds gauge
+kafka_consumer_fetch_throttle_time_max_seconds{client_id="consumer-1",} NaN
+# HELP system_load_average_1m The sum of the number of runnable entities queued to available processors and the number of runnable entities running on the available processors averaged over a period of time
+# TYPE system_load_average_1m gauge
+system_load_average_1m 0.52
+# HELP rabbitmq_acknowledged_published_total
+# TYPE rabbitmq_acknowledged_published_total counter
+rabbitmq_acknowledged_published_total{name="rabbit",} 0.0
+# HELP kafka_consumer_join_time_avg_seconds The average time taken for a group rejoin. This value can get as high as the configured session timeout for the consumer, but should usually be lower.
+# TYPE kafka_consumer_join_time_avg_seconds gauge
+kafka_consumer_join_time_avg_seconds{client_id="consumer-1",} NaN
+# HELP jdbc_connections_max
+# TYPE jdbc_connections_max gauge
+jdbc_connections_max{name="dataSource",} 10.0
+# HELP kafka_consumer_assigned_partitions The number of partitions currently assigned to this consumer.
+# TYPE kafka_consumer_assigned_partitions gauge
+kafka_consumer_assigned_partitions{client_id="consumer-1",} 0.0
+# HELP tomcat_sessions_rejected_sessions_total
+# TYPE tomcat_sessions_rejected_sessions_total counter
+tomcat_sessions_rejected_sessions_total 0.0
+
+# HELP kafka_consumer_heartbeat_response_time_max_seconds The max time taken to receive a response to a heartbeat request.
+# TYPE kafka_consumer_heartbeat_response_time_max_seconds gauge
+kafka_consumer_heartbeat_response_time_max_seconds{client_id="consumer-1",} NaN
+# HELP jvm_threads_daemon_threads The current number of live daemon threads
+# TYPE jvm_threads_daemon_threads gauge
+jvm_threads_daemon_threads 20.0
+# HELP system_cpu_count The number of processors available to the Java virtual machine
+# TYPE system_cpu_count gauge
+system_cpu_count 16.0
+# HELP jvm_buffer_count_buffers An estimate of the number of buffers in the pool
+# TYPE jvm_buffer_count_buffers gauge
+jvm_buffer_count_buffers{id="mapped",} 0.0
+jvm_buffer_count_buffers{id="direct",} 10.0
+# HELP kafka_consumer_io_wait_time_avg_seconds The average length of time the I/O thread spent waiting for a socket to be ready for reads or writes.
+# TYPE kafka_consumer_io_wait_time_avg_seconds gauge
+kafka_consumer_io_wait_time_avg_seconds{client_id="consumer-1",} 0.047184790159065626
+# HELP jvm_memory_max_bytes The maximum amount of memory in bytes that can be used for memory management
+# TYPE jvm_memory_max_bytes gauge
+jvm_memory_max_bytes{area="nonheap",id="CodeHeap 'profiled nmethods'",} 1.22028032E8
+jvm_memory_max_bytes{area="heap",id="G1 Survivor Space",} -1.0
+jvm_memory_max_bytes{area="heap",id="G1 Old Gen",} 8.331984896E9
+jvm_memory_max_bytes{area="nonheap",id="Metaspace",} -1.0
+jvm_memory_max_bytes{area="nonheap",id="CodeHeap 'non-nmethods'",} 7598080.0
+jvm_memory_max_bytes{area="heap",id="G1 Eden Space",} -1.0
+jvm_memory_max_bytes{area="nonheap",id="Compressed Class Space",} 1.073741824E9
+jvm_memory_max_bytes{area="nonheap",id="CodeHeap 'non-profiled nmethods'",} 1.22032128E8
+# HELP jvm_gc_pause_seconds Time spent in GC pause
+# TYPE jvm_gc_pause_seconds summary
+jvm_gc_pause_seconds_count{action="end of minor GC",cause="Metadata GC Threshold",} 2.0
+jvm_gc_pause_seconds_sum{action="end of minor GC",cause="Metadata GC Threshold",} 0.031
+jvm_gc_pause_seconds_count{action="end of minor GC",cause="G1 Evacuation Pause",} 1.0
+jvm_gc_pause_seconds_sum{action="end of minor GC",cause="G1 Evacuation Pause",} 0.016
+# HELP jvm_gc_pause_seconds_max Time spent in GC pause
+# TYPE jvm_gc_pause_seconds_max gauge
+jvm_gc_pause_seconds_max{action="end of minor GC",cause="Metadata GC Threshold",} 0.02
+jvm_gc_pause_seconds_max{action="end of minor GC",cause="G1 Evacuation Pause",} 0.0
+# HELP kafka_consumer_connection_count_connections The current number of active connections.
+# TYPE kafka_consumer_connection_count_connections gauge
+kafka_consumer_connection_count_connections{client_id="consumer-1",} 0.0
+# HELP jdbc_connections_active
+# TYPE jdbc_connections_active gauge
+jdbc_connections_active{name="dataSource",} 0.0
diff --git a/src/fluent-bit/lib/cmetrics/tests/data/issue_fluent_bit_6021.txt b/src/fluent-bit/lib/cmetrics/tests/data/issue_fluent_bit_6021.txt
new file mode 100644
index 000000000..4f8cb76ba
--- /dev/null
+++ b/src/fluent-bit/lib/cmetrics/tests/data/issue_fluent_bit_6021.txt
@@ -0,0 +1,221 @@
+# TYPE envoy_cluster_manager_cds_init_fetch_timeout counter
+envoy_cluster_manager_cds_init_fetch_timeout{} 0
+
+# TYPE envoy_cluster_manager_cds_update_attempt counter
+envoy_cluster_manager_cds_update_attempt{} 1
+
+# TYPE envoy_cluster_manager_cds_update_failure counter
+envoy_cluster_manager_cds_update_failure{} 0
+
+# TYPE envoy_http_downstream_cx_length_ms histogram
+envoy_http_downstream_cx_length_ms_bucket{envoy_http_conn_manager_prefix="admin",le="0.5"} 0
+envoy_http_downstream_cx_length_ms_bucket{envoy_http_conn_manager_prefix="admin",le="1"} 0
+envoy_http_downstream_cx_length_ms_bucket{envoy_http_conn_manager_prefix="admin",le="5"} 0
+envoy_http_downstream_cx_length_ms_bucket{envoy_http_conn_manager_prefix="admin",le="10"} 0
+envoy_http_downstream_cx_length_ms_bucket{envoy_http_conn_manager_prefix="admin",le="25"} 1
+envoy_http_downstream_cx_length_ms_bucket{envoy_http_conn_manager_prefix="admin",le="50"} 1
+envoy_http_downstream_cx_length_ms_bucket{envoy_http_conn_manager_prefix="admin",le="100"} 1
+envoy_http_downstream_cx_length_ms_bucket{envoy_http_conn_manager_prefix="admin",le="250"} 1
+envoy_http_downstream_cx_length_ms_bucket{envoy_http_conn_manager_prefix="admin",le="500"} 1
+envoy_http_downstream_cx_length_ms_bucket{envoy_http_conn_manager_prefix="admin",le="1000"} 1
+envoy_http_downstream_cx_length_ms_bucket{envoy_http_conn_manager_prefix="admin",le="2500"} 1
+envoy_http_downstream_cx_length_ms_bucket{envoy_http_conn_manager_prefix="admin",le="5000"} 1
+envoy_http_downstream_cx_length_ms_bucket{envoy_http_conn_manager_prefix="admin",le="10000"} 1
+envoy_http_downstream_cx_length_ms_bucket{envoy_http_conn_manager_prefix="admin",le="30000"} 1
+envoy_http_downstream_cx_length_ms_bucket{envoy_http_conn_manager_prefix="admin",le="60000"} 1
+envoy_http_downstream_cx_length_ms_bucket{envoy_http_conn_manager_prefix="admin",le="300000"} 1
+envoy_http_downstream_cx_length_ms_bucket{envoy_http_conn_manager_prefix="admin",le="600000"} 1
+envoy_http_downstream_cx_length_ms_bucket{envoy_http_conn_manager_prefix="admin",le="1800000"} 1
+envoy_http_downstream_cx_length_ms_bucket{envoy_http_conn_manager_prefix="admin",le="3600000"} 1
+envoy_http_downstream_cx_length_ms_bucket{envoy_http_conn_manager_prefix="admin",le="+Inf"} 1
+envoy_http_downstream_cx_length_ms_sum{envoy_http_conn_manager_prefix="admin"} 15.5
+envoy_http_downstream_cx_length_ms_count{envoy_http_conn_manager_prefix="admin"} 1
+envoy_http_downstream_cx_length_ms_bucket{envoy_http_conn_manager_prefix="ingress_http",le="0.5"} 0
+envoy_http_downstream_cx_length_ms_bucket{envoy_http_conn_manager_prefix="ingress_http",le="1"} 0
+envoy_http_downstream_cx_length_ms_bucket{envoy_http_conn_manager_prefix="ingress_http",le="5"} 0
+envoy_http_downstream_cx_length_ms_bucket{envoy_http_conn_manager_prefix="ingress_http",le="10"} 0
+envoy_http_downstream_cx_length_ms_bucket{envoy_http_conn_manager_prefix="ingress_http",le="25"} 0
+envoy_http_downstream_cx_length_ms_bucket{envoy_http_conn_manager_prefix="ingress_http",le="50"} 0
+envoy_http_downstream_cx_length_ms_bucket{envoy_http_conn_manager_prefix="ingress_http",le="100"} 0
+envoy_http_downstream_cx_length_ms_bucket{envoy_http_conn_manager_prefix="ingress_http",le="250"} 0
+envoy_http_downstream_cx_length_ms_bucket{envoy_http_conn_manager_prefix="ingress_http",le="500"} 0
+envoy_http_downstream_cx_length_ms_bucket{envoy_http_conn_manager_prefix="ingress_http",le="1000"} 0
+envoy_http_downstream_cx_length_ms_bucket{envoy_http_conn_manager_prefix="ingress_http",le="2500"} 0
+envoy_http_downstream_cx_length_ms_bucket{envoy_http_conn_manager_prefix="ingress_http",le="5000"} 0
+envoy_http_downstream_cx_length_ms_bucket{envoy_http_conn_manager_prefix="ingress_http",le="10000"} 0
+envoy_http_downstream_cx_length_ms_bucket{envoy_http_conn_manager_prefix="ingress_http",le="30000"} 0
+envoy_http_downstream_cx_length_ms_bucket{envoy_http_conn_manager_prefix="ingress_http",le="60000"} 0
+envoy_http_downstream_cx_length_ms_bucket{envoy_http_conn_manager_prefix="ingress_http",le="300000"} 0
+envoy_http_downstream_cx_length_ms_bucket{envoy_http_conn_manager_prefix="ingress_http",le="600000"} 0
+envoy_http_downstream_cx_length_ms_bucket{envoy_http_conn_manager_prefix="ingress_http",le="1800000"} 0
+envoy_http_downstream_cx_length_ms_bucket{envoy_http_conn_manager_prefix="ingress_http",le="3600000"} 0
+envoy_http_downstream_cx_length_ms_bucket{envoy_http_conn_manager_prefix="ingress_http",le="+Inf"} 0
+envoy_http_downstream_cx_length_ms_sum{envoy_http_conn_manager_prefix="ingress_http"} 0
+envoy_http_downstream_cx_length_ms_count{envoy_http_conn_manager_prefix="ingress_http"} 0
+
+# TYPE envoy_http_downstream_rq_time histogram
+envoy_http_downstream_rq_time_bucket{envoy_http_conn_manager_prefix="admin",le="0.5"} 0
+envoy_http_downstream_rq_time_bucket{envoy_http_conn_manager_prefix="admin",le="1"} 0
+envoy_http_downstream_rq_time_bucket{envoy_http_conn_manager_prefix="admin",le="5"} 10
+envoy_http_downstream_rq_time_bucket{envoy_http_conn_manager_prefix="admin",le="10"} 10
+envoy_http_downstream_rq_time_bucket{envoy_http_conn_manager_prefix="admin",le="25"} 10
+envoy_http_downstream_rq_time_bucket{envoy_http_conn_manager_prefix="admin",le="50"} 10
+envoy_http_downstream_rq_time_bucket{envoy_http_conn_manager_prefix="admin",le="100"} 10
+envoy_http_downstream_rq_time_bucket{envoy_http_conn_manager_prefix="admin",le="250"} 10
+envoy_http_downstream_rq_time_bucket{envoy_http_conn_manager_prefix="admin",le="500"} 10
+envoy_http_downstream_rq_time_bucket{envoy_http_conn_manager_prefix="admin",le="1000"} 10
+envoy_http_downstream_rq_time_bucket{envoy_http_conn_manager_prefix="admin",le="2500"} 10
+envoy_http_downstream_rq_time_bucket{envoy_http_conn_manager_prefix="admin",le="5000"} 10
+envoy_http_downstream_rq_time_bucket{envoy_http_conn_manager_prefix="admin",le="10000"} 10
+envoy_http_downstream_rq_time_bucket{envoy_http_conn_manager_prefix="admin",le="30000"} 10
+envoy_http_downstream_rq_time_bucket{envoy_http_conn_manager_prefix="admin",le="60000"} 10
+envoy_http_downstream_rq_time_bucket{envoy_http_conn_manager_prefix="admin",le="300000"} 10
+envoy_http_downstream_rq_time_bucket{envoy_http_conn_manager_prefix="admin",le="600000"} 10
+envoy_http_downstream_rq_time_bucket{envoy_http_conn_manager_prefix="admin",le="1800000"} 10
+envoy_http_downstream_rq_time_bucket{envoy_http_conn_manager_prefix="admin",le="3600000"} 10
+envoy_http_downstream_rq_time_bucket{envoy_http_conn_manager_prefix="admin",le="+Inf"} 10
+envoy_http_downstream_rq_time_sum{envoy_http_conn_manager_prefix="admin"} 25.5
+envoy_http_downstream_rq_time_count{envoy_http_conn_manager_prefix="admin"} 10
+envoy_http_downstream_rq_time_bucket{envoy_http_conn_manager_prefix="ingress_http",le="0.5"} 0
+envoy_http_downstream_rq_time_bucket{envoy_http_conn_manager_prefix="ingress_http",le="1"} 0
+envoy_http_downstream_rq_time_bucket{envoy_http_conn_manager_prefix="ingress_http",le="5"} 0
+envoy_http_downstream_rq_time_bucket{envoy_http_conn_manager_prefix="ingress_http",le="10"} 0
+envoy_http_downstream_rq_time_bucket{envoy_http_conn_manager_prefix="ingress_http",le="25"} 0
+envoy_http_downstream_rq_time_bucket{envoy_http_conn_manager_prefix="ingress_http",le="50"} 0
+envoy_http_downstream_rq_time_bucket{envoy_http_conn_manager_prefix="ingress_http",le="100"} 0
+envoy_http_downstream_rq_time_bucket{envoy_http_conn_manager_prefix="ingress_http",le="250"} 0
+envoy_http_downstream_rq_time_bucket{envoy_http_conn_manager_prefix="ingress_http",le="500"} 0
+envoy_http_downstream_rq_time_bucket{envoy_http_conn_manager_prefix="ingress_http",le="1000"} 0
+envoy_http_downstream_rq_time_bucket{envoy_http_conn_manager_prefix="ingress_http",le="2500"} 0
+envoy_http_downstream_rq_time_bucket{envoy_http_conn_manager_prefix="ingress_http",le="5000"} 0
+envoy_http_downstream_rq_time_bucket{envoy_http_conn_manager_prefix="ingress_http",le="10000"} 0
+envoy_http_downstream_rq_time_bucket{envoy_http_conn_manager_prefix="ingress_http",le="30000"} 0
+envoy_http_downstream_rq_time_bucket{envoy_http_conn_manager_prefix="ingress_http",le="60000"} 0
+envoy_http_downstream_rq_time_bucket{envoy_http_conn_manager_prefix="ingress_http",le="300000"} 0
+envoy_http_downstream_rq_time_bucket{envoy_http_conn_manager_prefix="ingress_http",le="600000"} 0
+envoy_http_downstream_rq_time_bucket{envoy_http_conn_manager_prefix="ingress_http",le="1800000"} 0
+envoy_http_downstream_rq_time_bucket{envoy_http_conn_manager_prefix="ingress_http",le="3600000"} 0
+envoy_http_downstream_rq_time_bucket{envoy_http_conn_manager_prefix="ingress_http",le="+Inf"} 0
+envoy_http_downstream_rq_time_sum{envoy_http_conn_manager_prefix="ingress_http"} 0
+envoy_http_downstream_rq_time_count{envoy_http_conn_manager_prefix="ingress_http"} 0
+
+# TYPE envoy_listener_admin_downstream_cx_length_ms histogram
+envoy_listener_admin_downstream_cx_length_ms_bucket{le="0.5"} 0
+envoy_listener_admin_downstream_cx_length_ms_bucket{le="1"} 0
+envoy_listener_admin_downstream_cx_length_ms_bucket{le="5"} 0
+envoy_listener_admin_downstream_cx_length_ms_bucket{le="10"} 0
+envoy_listener_admin_downstream_cx_length_ms_bucket{le="25"} 1
+envoy_listener_admin_downstream_cx_length_ms_bucket{le="50"} 1
+envoy_listener_admin_downstream_cx_length_ms_bucket{le="100"} 1
+envoy_listener_admin_downstream_cx_length_ms_bucket{le="250"} 1
+envoy_listener_admin_downstream_cx_length_ms_bucket{le="500"} 1
+envoy_listener_admin_downstream_cx_length_ms_bucket{le="1000"} 1
+envoy_listener_admin_downstream_cx_length_ms_bucket{le="2500"} 1
+envoy_listener_admin_downstream_cx_length_ms_bucket{le="5000"} 1
+envoy_listener_admin_downstream_cx_length_ms_bucket{le="10000"} 1
+envoy_listener_admin_downstream_cx_length_ms_bucket{le="30000"} 1
+envoy_listener_admin_downstream_cx_length_ms_bucket{le="60000"} 1
+envoy_listener_admin_downstream_cx_length_ms_bucket{le="300000"} 1
+envoy_listener_admin_downstream_cx_length_ms_bucket{le="600000"} 1
+envoy_listener_admin_downstream_cx_length_ms_bucket{le="1800000"} 1
+envoy_listener_admin_downstream_cx_length_ms_bucket{le="3600000"} 1
+envoy_listener_admin_downstream_cx_length_ms_bucket{le="+Inf"} 1
+envoy_listener_admin_downstream_cx_length_ms_sum{} 15.5
+envoy_listener_admin_downstream_cx_length_ms_count{} 1
+
+# TYPE envoy_listener_downstream_cx_length_ms histogram
+envoy_listener_downstream_cx_length_ms_bucket{envoy_listener_address="0.0.0.0_10000",le="0.5"} 0
+envoy_listener_downstream_cx_length_ms_bucket{envoy_listener_address="0.0.0.0_10000",le="1"} 0
+envoy_listener_downstream_cx_length_ms_bucket{envoy_listener_address="0.0.0.0_10000",le="5"} 0
+envoy_listener_downstream_cx_length_ms_bucket{envoy_listener_address="0.0.0.0_10000",le="10"} 0
+envoy_listener_downstream_cx_length_ms_bucket{envoy_listener_address="0.0.0.0_10000",le="25"} 0
+envoy_listener_downstream_cx_length_ms_bucket{envoy_listener_address="0.0.0.0_10000",le="50"} 0
+envoy_listener_downstream_cx_length_ms_bucket{envoy_listener_address="0.0.0.0_10000",le="100"} 0
+envoy_listener_downstream_cx_length_ms_bucket{envoy_listener_address="0.0.0.0_10000",le="250"} 0
+envoy_listener_downstream_cx_length_ms_bucket{envoy_listener_address="0.0.0.0_10000",le="500"} 0
+envoy_listener_downstream_cx_length_ms_bucket{envoy_listener_address="0.0.0.0_10000",le="1000"} 0
+envoy_listener_downstream_cx_length_ms_bucket{envoy_listener_address="0.0.0.0_10000",le="2500"} 0
+envoy_listener_downstream_cx_length_ms_bucket{envoy_listener_address="0.0.0.0_10000",le="5000"} 0
+envoy_listener_downstream_cx_length_ms_bucket{envoy_listener_address="0.0.0.0_10000",le="10000"} 0
+envoy_listener_downstream_cx_length_ms_bucket{envoy_listener_address="0.0.0.0_10000",le="30000"} 0
+envoy_listener_downstream_cx_length_ms_bucket{envoy_listener_address="0.0.0.0_10000",le="60000"} 0
+envoy_listener_downstream_cx_length_ms_bucket{envoy_listener_address="0.0.0.0_10000",le="300000"} 0
+envoy_listener_downstream_cx_length_ms_bucket{envoy_listener_address="0.0.0.0_10000",le="600000"} 0
+envoy_listener_downstream_cx_length_ms_bucket{envoy_listener_address="0.0.0.0_10000",le="1800000"} 0
+envoy_listener_downstream_cx_length_ms_bucket{envoy_listener_address="0.0.0.0_10000",le="3600000"} 0
+envoy_listener_downstream_cx_length_ms_bucket{envoy_listener_address="0.0.0.0_10000",le="+Inf"} 0
+envoy_listener_downstream_cx_length_ms_sum{envoy_listener_address="0.0.0.0_10000"} 0
+envoy_listener_downstream_cx_length_ms_count{envoy_listener_address="0.0.0.0_10000"} 0
+
+# TYPE envoy_listener_manager_lds_update_duration histogram
+envoy_listener_manager_lds_update_duration_bucket{le="0.5"} 0
+envoy_listener_manager_lds_update_duration_bucket{le="1"} 0
+envoy_listener_manager_lds_update_duration_bucket{le="5"} 0
+envoy_listener_manager_lds_update_duration_bucket{le="10"} 0
+envoy_listener_manager_lds_update_duration_bucket{le="25"} 0
+envoy_listener_manager_lds_update_duration_bucket{le="50"} 0
+envoy_listener_manager_lds_update_duration_bucket{le="100"} 0
+envoy_listener_manager_lds_update_duration_bucket{le="250"} 0
+envoy_listener_manager_lds_update_duration_bucket{le="500"} 0
+envoy_listener_manager_lds_update_duration_bucket{le="1000"} 0
+envoy_listener_manager_lds_update_duration_bucket{le="2500"} 0
+envoy_listener_manager_lds_update_duration_bucket{le="5000"} 0
+envoy_listener_manager_lds_update_duration_bucket{le="10000"} 0
+envoy_listener_manager_lds_update_duration_bucket{le="30000"} 0
+envoy_listener_manager_lds_update_duration_bucket{le="60000"} 0
+envoy_listener_manager_lds_update_duration_bucket{le="300000"} 0
+envoy_listener_manager_lds_update_duration_bucket{le="600000"} 0
+envoy_listener_manager_lds_update_duration_bucket{le="1800000"} 0
+envoy_listener_manager_lds_update_duration_bucket{le="3600000"} 0
+envoy_listener_manager_lds_update_duration_bucket{le="+Inf"} 0
+envoy_listener_manager_lds_update_duration_sum{} 0
+envoy_listener_manager_lds_update_duration_count{} 0
+
+# TYPE envoy_sds_tls_sds_update_duration histogram
+envoy_sds_tls_sds_update_duration_bucket{le="0.5"} 0
+envoy_sds_tls_sds_update_duration_bucket{le="1"} 0
+envoy_sds_tls_sds_update_duration_bucket{le="5"} 0
+envoy_sds_tls_sds_update_duration_bucket{le="10"} 0
+envoy_sds_tls_sds_update_duration_bucket{le="25"} 0
+envoy_sds_tls_sds_update_duration_bucket{le="50"} 0
+envoy_sds_tls_sds_update_duration_bucket{le="100"} 0
+envoy_sds_tls_sds_update_duration_bucket{le="250"} 0
+envoy_sds_tls_sds_update_duration_bucket{le="500"} 0
+envoy_sds_tls_sds_update_duration_bucket{le="1000"} 0
+envoy_sds_tls_sds_update_duration_bucket{le="2500"} 0
+envoy_sds_tls_sds_update_duration_bucket{le="5000"} 0
+envoy_sds_tls_sds_update_duration_bucket{le="10000"} 0
+envoy_sds_tls_sds_update_duration_bucket{le="30000"} 0
+envoy_sds_tls_sds_update_duration_bucket{le="60000"} 0
+envoy_sds_tls_sds_update_duration_bucket{le="300000"} 0
+envoy_sds_tls_sds_update_duration_bucket{le="600000"} 0
+envoy_sds_tls_sds_update_duration_bucket{le="1800000"} 0
+envoy_sds_tls_sds_update_duration_bucket{le="3600000"} 0
+envoy_sds_tls_sds_update_duration_bucket{le="+Inf"} 0
+envoy_sds_tls_sds_update_duration_sum{} 0
+envoy_sds_tls_sds_update_duration_count{} 0
+
+# TYPE envoy_server_initialization_time_ms histogram
+envoy_server_initialization_time_ms_bucket{le="0.5"} 0
+envoy_server_initialization_time_ms_bucket{le="1"} 0
+envoy_server_initialization_time_ms_bucket{le="5"} 0
+envoy_server_initialization_time_ms_bucket{le="10"} 0
+envoy_server_initialization_time_ms_bucket{le="25"} 0
+envoy_server_initialization_time_ms_bucket{le="50"} 1
+envoy_server_initialization_time_ms_bucket{le="100"} 1
+envoy_server_initialization_time_ms_bucket{le="250"} 1
+envoy_server_initialization_time_ms_bucket{le="500"} 1
+envoy_server_initialization_time_ms_bucket{le="1000"} 1
+envoy_server_initialization_time_ms_bucket{le="2500"} 1
+envoy_server_initialization_time_ms_bucket{le="5000"} 1
+envoy_server_initialization_time_ms_bucket{le="10000"} 1
+envoy_server_initialization_time_ms_bucket{le="30000"} 1
+envoy_server_initialization_time_ms_bucket{le="60000"} 1
+envoy_server_initialization_time_ms_bucket{le="300000"} 1
+envoy_server_initialization_time_ms_bucket{le="600000"} 1
+envoy_server_initialization_time_ms_bucket{le="1800000"} 1
+envoy_server_initialization_time_ms_bucket{le="3600000"} 1
+envoy_server_initialization_time_ms_bucket{le="+Inf"} 1
+envoy_server_initialization_time_ms_sum{} 30.5
+envoy_server_initialization_time_ms_count{} 1
+
diff --git a/src/fluent-bit/lib/cmetrics/tests/data/pr_168.txt b/src/fluent-bit/lib/cmetrics/tests/data/pr_168.txt
new file mode 100644
index 000000000..f6fa0ad0d
--- /dev/null
+++ b/src/fluent-bit/lib/cmetrics/tests/data/pr_168.txt
@@ -0,0 +1,22 @@
+# HELP prometheus_engine_query_duration_seconds Query timings
+# TYPE prometheus_engine_query_duration_seconds summary
+prometheus_engine_query_duration_seconds{slice="inner_eval",quantile="0.5"} NaN
+prometheus_engine_query_duration_seconds{slice="inner_eval",quantile="0.9"} NaN
+prometheus_engine_query_duration_seconds{slice="inner_eval",quantile="0.99"} NaN
+prometheus_engine_query_duration_seconds_sum{slice="inner_eval"} 0
+prometheus_engine_query_duration_seconds_count{slice="inner_eval"} 0
+prometheus_engine_query_duration_seconds{slice="prepare_time",quantile="0.5"} NaN
+prometheus_engine_query_duration_seconds{slice="prepare_time",quantile="0.9"} NaN
+prometheus_engine_query_duration_seconds{slice="prepare_time",quantile="0.99"} NaN
+prometheus_engine_query_duration_seconds_sum{slice="prepare_time"} 0
+prometheus_engine_query_duration_seconds_count{slice="prepare_time"} 0
+prometheus_engine_query_duration_seconds{slice="queue_time",quantile="0.5"} NaN
+prometheus_engine_query_duration_seconds{slice="queue_time",quantile="0.9"} NaN
+prometheus_engine_query_duration_seconds{slice="queue_time",quantile="0.99"} NaN
+prometheus_engine_query_duration_seconds_sum{slice="queue_time"} 0
+prometheus_engine_query_duration_seconds_count{slice="queue_time"} 0
+prometheus_engine_query_duration_seconds{slice="result_sort",quantile="0.5"} NaN
+prometheus_engine_query_duration_seconds{slice="result_sort",quantile="0.9"} NaN
+prometheus_engine_query_duration_seconds{slice="result_sort",quantile="0.99"} NaN
+prometheus_engine_query_duration_seconds_sum{slice="result_sort"} 0
+prometheus_engine_query_duration_seconds_count{slice="result_sort"} 0
diff --git a/src/fluent-bit/lib/cmetrics/tests/decoding.c b/src/fluent-bit/lib/cmetrics/tests/decoding.c
new file mode 100644
index 000000000..823861bdb
--- /dev/null
+++ b/src/fluent-bit/lib/cmetrics/tests/decoding.c
@@ -0,0 +1,184 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* CMetrics
+ * ========
+ * Copyright 2021-2022 The CMetrics Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cmetrics/cmetrics.h>
+#include <cmetrics/cmt_gauge.h>
+#include <cmetrics/cmt_counter.h>
+#include <cmetrics/cmt_summary.h>
+#include <cmetrics/cmt_histogram.h>
+#include <cmetrics/cmt_encode_prometheus.h>
+#include <cmetrics/cmt_decode_opentelemetry.h>
+#include <cmetrics/cmt_encode_opentelemetry.h>
+
+#include "cmt_tests.h"
+
+static struct cmt *generate_encoder_test_data()
+{
+ double quantiles[5];
+ struct cmt_histogram_buckets *buckets;
+ double val;
+ struct cmt *cmt;
+ uint64_t ts;
+ struct cmt_gauge *g1;
+ struct cmt_counter *c1;
+ struct cmt_summary *s1;
+ struct cmt_histogram *h1;
+
+ ts = 0;
+ cmt = cmt_create();
+
+ c1 = cmt_counter_create(cmt, "kubernetes", "network", "load_counter", "Network load counter",
+ 2, (char *[]) {"hostname", "app"});
+
+ cmt_counter_get_val(c1, 0, NULL, &val);
+ cmt_counter_inc(c1, ts, 0, NULL);
+ cmt_counter_add(c1, ts, 2, 0, NULL);
+ cmt_counter_get_val(c1, 0, NULL, &val);
+
+ cmt_counter_inc(c1, ts, 2, (char *[]) {"localhost", "cmetrics"});
+ cmt_counter_get_val(c1, 2, (char *[]) {"localhost", "cmetrics"}, &val);
+ cmt_counter_add(c1, ts, 10.55, 2, (char *[]) {"localhost", "test"});
+ cmt_counter_get_val(c1, 2, (char *[]) {"localhost", "test"}, &val);
+ cmt_counter_set(c1, ts, 12.15, 2, (char *[]) {"localhost", "test"});
+ cmt_counter_set(c1, ts, 1, 2, (char *[]) {"localhost", "test"});
+
+ g1 = cmt_gauge_create(cmt, "kubernetes", "network", "load_gauge", "Network load gauge", 0, NULL);
+
+ cmt_gauge_get_val(g1, 0, NULL, &val);
+ cmt_gauge_set(g1, ts, 2.0, 0, NULL);
+ cmt_gauge_get_val(g1, 0, NULL, &val);
+ cmt_gauge_inc(g1, ts, 0, NULL);
+ cmt_gauge_get_val(g1, 0, NULL, &val);
+ cmt_gauge_sub(g1, ts, 2, 0, NULL);
+ cmt_gauge_get_val(g1, 0, NULL, &val);
+ cmt_gauge_dec(g1, ts, 0, NULL);
+ cmt_gauge_get_val(g1, 0, NULL, &val);
+ cmt_gauge_inc(g1, ts, 0, NULL);
+
+ buckets = cmt_histogram_buckets_create(3, 0.05, 5.0, 10.0);
+
+ h1 = cmt_histogram_create(cmt,
+ "k8s", "network", "load_histogram", "Network load histogram",
+ buckets,
+ 1, (char *[]) {"my_label"});
+
+ cmt_histogram_observe(h1, ts, 0.001, 0, NULL);
+ cmt_histogram_observe(h1, ts, 0.020, 0, NULL);
+ cmt_histogram_observe(h1, ts, 5.0, 0, NULL);
+ cmt_histogram_observe(h1, ts, 8.0, 0, NULL);
+ cmt_histogram_observe(h1, ts, 1000, 0, NULL);
+
+ cmt_histogram_observe(h1, ts, 0.001, 1, (char *[]) {"my_val"});
+ cmt_histogram_observe(h1, ts, 0.020, 1, (char *[]) {"my_val"});
+ cmt_histogram_observe(h1, ts, 5.0, 1, (char *[]) {"my_val"});
+ cmt_histogram_observe(h1, ts, 8.0, 1, (char *[]) {"my_val"});
+ cmt_histogram_observe(h1, ts, 1000, 1, (char *[]) {"my_val"});;
+
+ quantiles[0] = 0.1;
+ quantiles[1] = 0.2;
+ quantiles[2] = 0.3;
+ quantiles[3] = 0.4;
+ quantiles[4] = 0.5;
+
+ s1 = cmt_summary_create(cmt,
+ "k8s", "disk", "load_summary", "Disk load summary",
+ 5, quantiles,
+ 1, (char *[]) {"my_label"});
+
+ quantiles[0] = 1.1;
+ quantiles[1] = 2.2;
+ quantiles[2] = 3.3;
+ quantiles[3] = 4.4;
+ quantiles[4] = 5.5;
+
+ cmt_summary_set_default(s1, ts, quantiles, 51.612894511314444, 10, 0, NULL);
+
+ quantiles[0] = 11.11;
+ quantiles[1] = 0;
+ quantiles[2] = 33.33;
+ quantiles[3] = 44.44;
+ quantiles[4] = 55.55;
+
+ cmt_summary_set_default(s1, ts, quantiles, 51.612894511314444, 10, 1, (char *[]) {"my_val"});
+
+ return cmt;
+}
+
+void test_opentelemetry()
+{
+ cfl_sds_t reference_prometheus_context;
+ cfl_sds_t opentelemetry_context;
+ struct cfl_list decoded_context_list;
+ cfl_sds_t prometheus_context;
+ struct cmt *decoded_context;
+ size_t offset;
+ int result;
+ struct cmt *cmt;
+
+ offset = 0;
+
+ cmt_initialize();
+
+ cmt = generate_encoder_test_data();
+ TEST_CHECK(cmt != NULL);
+
+ reference_prometheus_context = cmt_encode_prometheus_create(cmt, CMT_TRUE);
+ TEST_CHECK(reference_prometheus_context != NULL);
+
+ if (reference_prometheus_context != NULL) {
+ opentelemetry_context = cmt_encode_opentelemetry_create(cmt);
+ TEST_CHECK(opentelemetry_context != NULL);
+
+ if (opentelemetry_context != NULL) {
+ result = cmt_decode_opentelemetry_create(&decoded_context_list,
+ opentelemetry_context,
+ cfl_sds_len(opentelemetry_context),
+ &offset);
+
+ if (TEST_CHECK(result == 0)) {
+ decoded_context = cfl_list_entry_first(&decoded_context_list, struct cmt, _head);
+
+ if (TEST_CHECK(result == 0)) {
+ prometheus_context = cmt_encode_prometheus_create(decoded_context,
+ CMT_TRUE);
+ TEST_CHECK(prometheus_context != NULL);
+
+ if (prometheus_context != NULL) {
+ TEST_CHECK(strcmp(prometheus_context,
+ reference_prometheus_context) == 0);
+
+ cmt_encode_prometheus_destroy(prometheus_context);
+ }
+ }
+
+ cmt_decode_opentelemetry_destroy(&decoded_context_list);
+ }
+ }
+
+ cmt_encode_opentelemetry_destroy(opentelemetry_context);
+ cmt_encode_prometheus_destroy(reference_prometheus_context);
+ }
+
+ cmt_destroy(cmt);
+}
+
+TEST_LIST = {
+ {"opentelemetry", test_opentelemetry},
+ { 0 }
+};
diff --git a/src/fluent-bit/lib/cmetrics/tests/encode_output.c b/src/fluent-bit/lib/cmetrics/tests/encode_output.c
new file mode 100644
index 000000000..7d54ebfaf
--- /dev/null
+++ b/src/fluent-bit/lib/cmetrics/tests/encode_output.c
@@ -0,0 +1,67 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* CMetrics
+ * ========
+ * Copyright 2021-2022 The CMetrics Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+/*
+ * This file is a helper utility just to try out all the encoders: given a specific
+ * CMetrics context, encode to all possible formats and destroy them. This is
+ * useful to trap potential memory leaks or errors when doing changes to the
+ * metric types.
+ */
+
+#include <cmetrics/cmetrics.h>
+#include <cmetrics/cmt_encode_msgpack.h>
+#include <cmetrics/cmt_encode_prometheus_remote_write.h>
+#include <cmetrics/cmt_encode_prometheus.h>
+#include <cmetrics/cmt_encode_opentelemetry.h>
+#include <cmetrics/cmt_encode_text.h>
+#include <cmetrics/cmt_encode_influx.h>
+
+int cmt_test_encode_all(struct cmt *cmt)
+{
+ char *out_buf;
+ size_t out_size;
+ cfl_sds_t sds_buf;
+
+ /* text */
+ sds_buf = cmt_encode_text_create(cmt);
+ cmt_encode_text_destroy(sds_buf);
+
+ /* prometheus */
+ sds_buf = cmt_encode_prometheus_create(cmt, CMT_TRUE);
+ cmt_encode_prometheus_destroy(sds_buf);
+
+ /* prometheus remote write */
+ sds_buf = cmt_encode_prometheus_remote_write_create(cmt);
+ cmt_encode_prometheus_remote_write_destroy(sds_buf);
+
+ /* msgpack */
+ cmt_encode_msgpack_create(cmt, &out_buf, &out_size);
+ cmt_encode_msgpack_destroy(out_buf);
+
+ /* influx */
+ sds_buf = cmt_encode_influx_create(cmt);
+ cmt_encode_influx_destroy(sds_buf);
+
+ /* opentelemetry */
+ sds_buf = cmt_encode_opentelemetry_create(cmt);
+ cmt_encode_opentelemetry_destroy(sds_buf);
+
+ return 0;
+}
diff --git a/src/fluent-bit/lib/cmetrics/tests/encode_output.h b/src/fluent-bit/lib/cmetrics/tests/encode_output.h
new file mode 100644
index 000000000..929ea1a33
--- /dev/null
+++ b/src/fluent-bit/lib/cmetrics/tests/encode_output.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* CMetrics
+ * ========
+ * Copyright 2021-2022 The CMetrics Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CMT_TESTS_ENCODE_OUTPUT_H
+#define CMT_TESTS_ENCODE_OUTPUT_H
+
+#include <cmetrics/cmetrics.h>
+
+int cmt_test_encode_all(struct cmt *cmt);
+
+#endif
+
diff --git a/src/fluent-bit/lib/cmetrics/tests/encoding.c b/src/fluent-bit/lib/cmetrics/tests/encoding.c
new file mode 100644
index 000000000..77a580273
--- /dev/null
+++ b/src/fluent-bit/lib/cmetrics/tests/encoding.c
@@ -0,0 +1,1056 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* CMetrics
+ * ========
+ * Copyright 2021-2022 The CMetrics Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifdef __GNUC__
+#define _GNU_SOURCE
+#endif
+
+#include <cmetrics/cmetrics.h>
+#include <cmetrics/cmt_gauge.h>
+#include <cmetrics/cmt_counter.h>
+#include <cmetrics/cmt_summary.h>
+#include <cmetrics/cmt_histogram.h>
+#include <cmetrics/cmt_encode_msgpack.h>
+#include <cmetrics/cmt_decode_msgpack.h>
+#include <cmetrics/cmt_encode_prometheus_remote_write.h>
+#include <cmetrics/cmt_encode_prometheus.h>
+#include <cmetrics/cmt_encode_opentelemetry.h>
+#include <cmetrics/cmt_encode_text.h>
+#include <cmetrics/cmt_encode_influx.h>
+#include <cmetrics/cmt_encode_splunk_hec.h>
+
+#include "cmt_tests.h"
+
+static struct cmt *generate_simple_encoder_test_data()
+{
+ double val;
+ uint64_t ts;
+ struct cmt *cmt;
+ struct cmt_counter *c;
+
+ cmt = cmt_create();
+
+ c = cmt_counter_create(cmt, "kubernetes", "network", "load", "Network load",
+ 2, (char *[]) {"hostname", "app"});
+
+ ts = 0;
+
+ cmt_counter_get_val(c, 0, NULL, &val);
+ cmt_counter_inc(c, ts, 0, NULL);
+ cmt_counter_add(c, ts, 2, 0, NULL);
+ cmt_counter_get_val(c, 0, NULL, &val);
+
+ cmt_counter_inc(c, ts, 2, (char *[]) {"localhost", "cmetrics"});
+ cmt_counter_get_val(c, 2, (char *[]) {"localhost", "cmetrics"}, &val);
+ cmt_counter_add(c, ts, 10.55, 2, (char *[]) {"localhost", "test"});
+ cmt_counter_get_val(c, 2, (char *[]) {"localhost", "test"}, &val);
+ cmt_counter_set(c, ts, 12.15, 2, (char *[]) {"localhost", "test"});
+ cmt_counter_set(c, ts, 1, 2, (char *[]) {"localhost", "test"});
+
+ return cmt;
+}
+
+static struct cmt *generate_encoder_test_data()
+{
+ double quantiles[5];
+ struct cmt_histogram_buckets *buckets;
+ double val;
+ struct cmt *cmt;
+ uint64_t ts;
+ struct cmt_gauge *g1;
+ struct cmt_counter *c1;
+ struct cmt_summary *s1;
+ struct cmt_histogram *h1;
+
+ ts = 0;
+ cmt = cmt_create();
+
+ c1 = cmt_counter_create(cmt, "kubernetes", "network", "load_counter", "Network load counter",
+ 2, (char *[]) {"hostname", "app"});
+
+ cmt_counter_get_val(c1, 0, NULL, &val);
+ cmt_counter_inc(c1, ts, 0, NULL);
+ cmt_counter_add(c1, ts, 2, 0, NULL);
+ cmt_counter_get_val(c1, 0, NULL, &val);
+
+ cmt_counter_inc(c1, ts, 2, (char *[]) {"localhost", "cmetrics"});
+ cmt_counter_get_val(c1, 2, (char *[]) {"localhost", "cmetrics"}, &val);
+ cmt_counter_add(c1, ts, 10.55, 2, (char *[]) {"localhost", "test"});
+ cmt_counter_get_val(c1, 2, (char *[]) {"localhost", "test"}, &val);
+ cmt_counter_set(c1, ts, 12.15, 2, (char *[]) {"localhost", "test"});
+ cmt_counter_set(c1, ts, 1, 2, (char *[]) {"localhost", "test"});
+
+ g1 = cmt_gauge_create(cmt, "kubernetes", "network", "load_gauge", "Network load gauge", 0, NULL);
+
+ cmt_gauge_get_val(g1, 0, NULL, &val);
+ cmt_gauge_set(g1, ts, 2.0, 0, NULL);
+ cmt_gauge_get_val(g1, 0, NULL, &val);
+ cmt_gauge_inc(g1, ts, 0, NULL);
+ cmt_gauge_get_val(g1, 0, NULL, &val);
+ cmt_gauge_sub(g1, ts, 2, 0, NULL);
+ cmt_gauge_get_val(g1, 0, NULL, &val);
+ cmt_gauge_dec(g1, ts, 0, NULL);
+ cmt_gauge_get_val(g1, 0, NULL, &val);
+ cmt_gauge_inc(g1, ts, 0, NULL);
+
+ buckets = cmt_histogram_buckets_create(3, 0.05, 5.0, 10.0);
+
+ h1 = cmt_histogram_create(cmt,
+ "k8s", "network", "load_histogram", "Network load histogram",
+ buckets,
+ 1, (char *[]) {"my_label"});
+
+ cmt_histogram_observe(h1, ts, 0.001, 0, NULL);
+ cmt_histogram_observe(h1, ts, 0.020, 0, NULL);
+ cmt_histogram_observe(h1, ts, 5.0, 0, NULL);
+ cmt_histogram_observe(h1, ts, 8.0, 0, NULL);
+ cmt_histogram_observe(h1, ts, 1000, 0, NULL);
+
+ cmt_histogram_observe(h1, ts, 0.001, 1, (char *[]) {"my_val"});
+ cmt_histogram_observe(h1, ts, 0.020, 1, (char *[]) {"my_val"});
+ cmt_histogram_observe(h1, ts, 5.0, 1, (char *[]) {"my_val"});
+ cmt_histogram_observe(h1, ts, 8.0, 1, (char *[]) {"my_val"});
+ cmt_histogram_observe(h1, ts, 1000, 1, (char *[]) {"my_val"});;
+
+ quantiles[0] = 0.1;
+ quantiles[1] = 0.2;
+ quantiles[2] = 0.3;
+ quantiles[3] = 0.4;
+ quantiles[4] = 0.5;
+
+ s1 = cmt_summary_create(cmt,
+ "k8s", "disk", "load_summary", "Disk load summary",
+ 5, quantiles,
+ 1, (char *[]) {"my_label"});
+
+ quantiles[0] = 1.1;
+ quantiles[1] = 2.2;
+ quantiles[2] = 3.3;
+ quantiles[3] = 4.4;
+ quantiles[4] = 5.5;
+
+ cmt_summary_set_default(s1, ts, quantiles, 51.612894511314444, 10, 0, NULL);
+
+ quantiles[0] = 11.11;
+ quantiles[1] = 0;
+ quantiles[2] = 33.33;
+ quantiles[3] = 44.44;
+ quantiles[4] = 55.55;
+
+ cmt_summary_set_default(s1, ts, quantiles, 51.612894511314444, 10, 1, (char *[]) {"my_val"});
+
+ return cmt;
+}
+
+/*
+ * perform the following data encoding and compare msgpack buffsers
+ *
+ * CMT -> MSGPACK -> CMT -> MSGPACK
+ * | |
+ * |---> compare <----|
+ */
+
+void test_cmt_to_msgpack()
+{
+ int ret;
+ size_t offset = 0;
+ char *mp1_buf = NULL;
+ size_t mp1_size = 0;
+ char *mp2_buf = NULL;
+ size_t mp2_size = 0;
+ struct cmt *cmt1 = NULL;
+ struct cmt *cmt2 = NULL;
+
+ cmt_initialize();
+
+ /* Generate context with data */
+ cmt1 = generate_encoder_test_data();
+ TEST_CHECK(cmt1 != NULL);
+
+ /* CMT1 -> Msgpack */
+ ret = cmt_encode_msgpack_create(cmt1, &mp1_buf, &mp1_size);
+ TEST_CHECK(ret == 0);
+
+ /* Msgpack -> CMT2 */
+ ret = cmt_decode_msgpack_create(&cmt2, mp1_buf, mp1_size, &offset);
+ TEST_CHECK(ret == 0);
+
+ /* CMT2 -> Msgpack */
+ ret = cmt_encode_msgpack_create(cmt2, &mp2_buf, &mp2_size);
+ TEST_CHECK(ret == 0);
+
+ /* Compare msgpacks */
+ TEST_CHECK(mp1_size == mp2_size);
+ if (mp1_size == mp2_size) {
+ TEST_CHECK(memcmp(mp1_buf, mp2_buf, mp1_size) == 0);
+ }
+
+ cmt_destroy(cmt1);
+ cmt_decode_msgpack_destroy(cmt2);
+ cmt_encode_msgpack_destroy(mp1_buf);
+ cmt_encode_msgpack_destroy(mp2_buf);
+}
+
+/*
+ * Encode a context, corrupt the last metric in the msgpack packet
+ * and invoke the decoder to verify if there are any leaks.
+ *
+ * CMT -> MSGPACK -> CMT
+ *
+ * Note: this function is meant to be executed in linux while using
+ * valgrind
+ */
+
+void test_cmt_to_msgpack_cleanup_on_error()
+{
+#ifdef __linux__
+ int ret;
+ size_t offset = 0;
+ char *mp1_buf = NULL;
+ size_t mp1_size = 0;
+ struct cmt *cmt1 = NULL;
+ struct cmt *cmt2 = NULL;
+ char *key_buffer = NULL;
+ char *key_haystack = NULL;
+
+ cmt_initialize();
+
+ /* Generate context with data */
+ cmt1 = generate_encoder_test_data();
+ TEST_CHECK(cmt1 != NULL);
+
+ /* CMT1 -> Msgpack */
+ ret = cmt_encode_msgpack_create(cmt1, &mp1_buf, &mp1_size);
+ TEST_CHECK(ret == 0);
+
+ key_haystack = &mp1_buf[mp1_size - 32];
+ key_buffer = memmem(key_haystack, 32, "hash", 4);
+
+ TEST_CHECK(key_buffer != NULL);
+
+ /* This turns the last 'hash' entry into 'hasq' which causes
+ * the map consumer in the decoder to detect an unprocessed entry
+ * and abort in `unpack_metric` which means a lot of allocations
+ * have been made including but not limited to temporary
+ * histogram bucket arrays and completely decoded histograms
+ */
+ key_buffer[3] = 'q';
+
+ /* Msgpack -> CMT2 */
+ ret = cmt_decode_msgpack_create(&cmt2, mp1_buf, mp1_size, &offset);
+
+ cmt_destroy(cmt1);
+ cmt_encode_msgpack_destroy(mp1_buf);
+
+ TEST_CHECK(ret != 0);
+ TEST_CHECK(cmt2 == NULL);
+
+#endif
+}
+
+/*
+ * perform the following data encoding and compare msgpack buffsers
+ *
+ * CMT -> MSGPACK -> CMT -> TEXT
+ * CMT -> TEXT
+ * | |
+ * |---> compare <----|
+ */
+void test_cmt_to_msgpack_integrity()
+{
+ int ret;
+ size_t offset = 0;
+ char *mp1_buf = NULL;
+ size_t mp1_size = 0;
+ char *text1_buf = NULL;
+ size_t text1_size = 0;
+ char *text2_buf = NULL;
+ size_t text2_size = 0;
+ struct cmt *cmt1 = NULL;
+ struct cmt *cmt2 = NULL;
+
+ /* Generate context with data */
+ cmt1 = generate_encoder_test_data();
+ TEST_CHECK(cmt1 != NULL);
+
+ /* CMT1 -> Msgpack */
+ ret = cmt_encode_msgpack_create(cmt1, &mp1_buf, &mp1_size);
+ TEST_CHECK(ret == 0);
+
+ /* Msgpack -> CMT2 */
+ ret = cmt_decode_msgpack_create(&cmt2, mp1_buf, mp1_size, &offset);
+ TEST_CHECK(ret == 0);
+
+ /* CMT1 -> Text */
+ text1_buf = cmt_encode_text_create(cmt1);
+ TEST_CHECK(text1_buf != NULL);
+ text1_size = cfl_sds_len(text1_buf);
+
+ /* CMT2 -> Text */
+ text2_buf = cmt_encode_text_create(cmt2);
+ TEST_CHECK(text2_buf != NULL);
+ text2_size = cfl_sds_len(text2_buf);
+
+ /* Compare msgpacks */
+ TEST_CHECK(text1_size == text2_size);
+ TEST_CHECK(memcmp(text1_buf, text2_buf, text1_size) == 0);
+
+ cmt_destroy(cmt1);
+
+ cmt_decode_msgpack_destroy(cmt2);
+ cmt_encode_msgpack_destroy(mp1_buf);
+
+ cmt_encode_text_destroy(text1_buf);
+ cmt_encode_text_destroy(text2_buf);
+}
+
+void test_cmt_msgpack_partial_processing()
+{
+ int ret = 0;
+ int iteration = 0;
+ size_t offset = 0;
+ char *mp1_buf = NULL;
+ size_t mp1_size = 0;
+ struct cmt *cmt1 = NULL;
+ struct cmt *cmt2 = NULL;
+ double base_counter_value = 0;
+ size_t expected_gauge_count = 0;
+ double current_counter_value = 0;
+ size_t expected_counter_count = 0;
+ struct cmt_counter *first_counter = NULL;
+ cfl_sds_t serialized_data_buffer = NULL;
+ size_t serialized_data_buffer_length = 0;
+
+ /* Generate an encoder context with more than one counter */
+ cmt1 = generate_encoder_test_data();
+ TEST_CHECK(NULL != cmt1);
+
+ /* Find the first counter so we can get its value before re-encoding it N times
+ * for the test, that way we can ensure that the decoded contexts we get in the
+ * next phase are individual ones and not just a glitch
+ */
+
+ first_counter = cfl_list_entry_first(&cmt1->counters, struct cmt_counter, _head);
+ TEST_CHECK(NULL != first_counter);
+
+ ret = cmt_counter_get_val(first_counter, 0, NULL, &base_counter_value);
+ TEST_CHECK(0 == ret);
+
+ expected_counter_count = cfl_list_size(&cmt1->counters);
+ expected_gauge_count = cfl_list_size(&cmt1->gauges);
+
+ /* Since we are modifying the counter on each iteration we have to re-encode it */
+ for (iteration = 0 ;
+ iteration < MSGPACK_PARTIAL_PROCESSING_ELEMENT_COUNT ;
+ iteration++) {
+
+ ret = cmt_counter_inc(first_counter, 0, 0, NULL);
+ TEST_CHECK(0 == ret);
+
+ ret = cmt_encode_msgpack_create(cmt1, &mp1_buf, &mp1_size);
+ TEST_CHECK(0 == ret);
+
+ if (NULL == serialized_data_buffer) {
+ serialized_data_buffer = cfl_sds_create_len(mp1_buf, mp1_size);
+ TEST_CHECK(NULL != serialized_data_buffer);
+ }
+ else {
+ cfl_sds_cat_safe(&serialized_data_buffer, mp1_buf, mp1_size);
+ /* TEST_CHECK(0 == ret); */
+ }
+
+ cmt_encode_msgpack_destroy(mp1_buf);
+ }
+
+ cmt_destroy(cmt1);
+
+ /* In this phase we invoke the decoder with until it retunrs an error indicating that
+ * there is not enough data in the input buffer, for each cycle we compare the value
+ * for the first counter which should be be incremental.
+ *
+ * We also check that the iteration count matches the pre established count.
+ */
+
+ ret = 0;
+ offset = 0;
+ iteration = 0;
+ serialized_data_buffer_length = cfl_sds_len(serialized_data_buffer);
+
+ while (CMT_DECODE_MSGPACK_SUCCESS == ret) {
+ ret = cmt_decode_msgpack_create(&cmt2, serialized_data_buffer,
+ serialized_data_buffer_length, &offset);
+
+ if (CMT_DECODE_MSGPACK_INSUFFICIENT_DATA == ret) {
+ break;
+ }
+ else if (CMT_DECODE_MSGPACK_SUCCESS != ret) {
+ break;
+ }
+
+ TEST_CHECK(0 == ret);
+
+ first_counter = cfl_list_entry_first(&cmt2->counters, struct cmt_counter, _head);
+ TEST_CHECK(NULL != first_counter);
+
+ ret = cmt_counter_get_val(first_counter, 0, NULL, &current_counter_value);
+ TEST_CHECK(0 == ret);
+
+ TEST_CHECK(base_counter_value == (current_counter_value - iteration - 1));
+
+ TEST_CHECK(expected_counter_count == cfl_list_size(&cmt2->counters));
+ TEST_CHECK(expected_gauge_count == cfl_list_size(&cmt2->gauges));
+
+ cmt_decode_msgpack_destroy(cmt2);
+
+ iteration++;
+ }
+
+ TEST_CHECK(MSGPACK_PARTIAL_PROCESSING_ELEMENT_COUNT == iteration);
+
+ cfl_sds_destroy(serialized_data_buffer);
+}
+
+void test_cmt_to_msgpack_stability()
+{
+ int ret = 0;
+ int iteration = 0;
+ size_t offset = 0;
+ char *mp1_buf = NULL;
+ size_t mp1_size = 0;
+ struct cmt *cmt1 = NULL;
+ struct cmt *cmt2 = NULL;
+
+ for (iteration = 0 ; iteration < MSGPACK_STABILITY_TEST_ITERATION_COUNT ; iteration++) {
+ cmt1 = generate_encoder_test_data();
+ TEST_CHECK(cmt1 != NULL);
+
+ ret = cmt_encode_msgpack_create(cmt1, &mp1_buf, &mp1_size);
+ TEST_CHECK(ret == 0);
+
+ offset = 0;
+ ret = cmt_decode_msgpack_create(&cmt2, mp1_buf, mp1_size, &offset);
+ TEST_CHECK(ret == 0);
+
+ cmt_destroy(cmt1);
+ cmt_decode_msgpack_destroy(cmt2);
+ cmt_encode_msgpack_destroy(mp1_buf);
+ }
+
+}
+
+void test_cmt_to_msgpack_labels()
+{
+ int ret;
+ size_t offset = 0;
+ char *mp1_buf = NULL;
+ size_t mp1_size = 1;
+ char *mp2_buf = NULL;
+ size_t mp2_size = 2;
+ struct cmt *cmt1 = NULL;
+ struct cmt *cmt2 = NULL;
+ cfl_sds_t text_result;
+ const char expected_text[] = "1970-01-01T00:00:00.000000000Z kubernetes_network_load{dev=\"Calyptia\",lang=\"C\"} = 3\n" \
+ "1970-01-01T00:00:00.000000000Z kubernetes_network_load{dev=\"Calyptia\",lang=\"C\",hostname=\"localhost\",app=\"cmetrics\"} = 1\n" \
+ "1970-01-01T00:00:00.000000000Z kubernetes_network_load{dev=\"Calyptia\",lang=\"C\",hostname=\"localhost\",app=\"test\"} = 12.15\n";
+
+ cmt_initialize();
+
+ /* Generate context with data */
+ cmt1 = generate_simple_encoder_test_data();
+ TEST_CHECK(NULL != cmt1);
+
+ /* CMT1 -> Msgpack */
+ ret = cmt_encode_msgpack_create(cmt1, &mp1_buf, &mp1_size);
+ TEST_CHECK(0 == ret);
+
+ /* Msgpack -> CMT2 */
+ ret = cmt_decode_msgpack_create(&cmt2, mp1_buf, mp1_size, &offset);
+ TEST_CHECK(0 == ret);
+
+ /* CMT2 -> Msgpack */
+ ret = cmt_encode_msgpack_create(cmt2, &mp2_buf, &mp2_size);
+ TEST_CHECK(0 == ret);
+
+ /* Compare msgpacks */
+ TEST_CHECK(mp1_size == mp2_size);
+ TEST_CHECK(0 == memcmp(mp1_buf, mp2_buf, mp1_size));
+
+ /* append static labels */
+ cmt_label_add(cmt2, "dev", "Calyptia");
+ cmt_label_add(cmt2, "lang", "C");
+
+ text_result = cmt_encode_text_create(cmt2);
+ TEST_CHECK(NULL != text_result);
+ TEST_CHECK(0 == strcmp(text_result, expected_text));
+
+ cmt_destroy(cmt1);
+ cmt_encode_text_destroy(text_result);
+ cmt_decode_msgpack_destroy(cmt2);
+ cmt_encode_msgpack_destroy(mp1_buf);
+ cmt_encode_msgpack_destroy(mp2_buf);
+}
+
+void test_prometheus_remote_write()
+{
+ struct cmt *cmt;
+ cfl_sds_t payload;
+ FILE *sample_file;
+
+ cmt_initialize();
+
+ cmt = generate_encoder_test_data();
+
+ payload = cmt_encode_prometheus_remote_write_create(cmt);
+ TEST_CHECK(NULL != payload);
+
+ if (payload == NULL) {
+ cmt_destroy(cmt);
+
+ return;
+ }
+
+ printf("\n\nDumping remote write payload to prometheus_remote_write_payload.bin, in order to test it \
+we need to compress it using snappys scmd :\n\
+scmd -c prometheus_remote_write_payload.bin prometheus_remote_write_payload.snp\n\n\
+and then send it using curl :\n\
+curl -v 'http://localhost:9090/receive' -H 'Content-Type: application/x-protobuf' \
+-H 'X-Prometheus-Remote-Write-Version: 0.1.0' -H 'User-Agent: metrics-worker' \
+--data-binary '@prometheus_remote_write_payload.snp'\n\n");
+
+ sample_file = fopen("prometheus_remote_write_payload.bin", "wb+");
+
+ fwrite(payload, 1, cfl_sds_len(payload), sample_file);
+
+ fclose(sample_file);
+
+ cmt_encode_prometheus_remote_write_destroy(payload);
+
+ cmt_destroy(cmt);
+}
+
+void test_opentelemetry()
+{
+ cfl_sds_t payload;
+ struct cmt *cmt;
+ FILE *sample_file;
+
+ cmt_initialize();
+
+ cmt = generate_encoder_test_data();
+
+ payload = cmt_encode_opentelemetry_create(cmt);
+ TEST_CHECK(NULL != payload);
+
+ if (payload == NULL) {
+ cmt_destroy(cmt);
+
+ return;
+ }
+
+ printf("\n\nDumping remote write payload to opentelemetry_payload.bin, in order to test it \
+we need to send it to our opentelemetry http endpoint using curl :\n\
+curl -v 'http://localhost:9090/v1/metrics' -H 'Content-Type: application/x-protobuf' \
+-H 'User-Agent: metrics-worker' \
+--data-binary '@opentelemetry_payload.bin'\n\n");
+
+ sample_file = fopen("opentelemetry_payload.bin", "wb+");
+
+ fwrite(payload, 1, cfl_sds_len(payload), sample_file);
+
+ fclose(sample_file);
+
+ cmt_encode_prometheus_remote_write_destroy(payload);
+
+ cmt_destroy(cmt);
+}
+
+void test_prometheus()
+{
+ uint64_t ts;
+ cfl_sds_t text;
+ struct cmt *cmt;
+ struct cmt_counter *c;
+
+ char *out1 = "# HELP cmt_labels_test \"Static\\\\ labels \\ntest\n"
+ "# TYPE cmt_labels_test counter\n"
+ "cmt_labels_test 1 0\n"
+ "cmt_labels_test{host=\"calyptia.com\",app=\"cmetrics\"} 2 0\n"
+ "cmt_labels_test{host=\"\\\"calyptia.com\\\"\",app=\"cme\\\\tr\\nics\"} 1 0\n";
+
+ char *out2 = "# HELP cmt_labels_test \"Static\\\\ labels \\ntest\n"
+ "# TYPE cmt_labels_test counter\n"
+ "cmt_labels_test{dev=\"Calyptia\",lang=\"C\\\"\\\\\\n\"} 1 0\n"
+ "cmt_labels_test{dev=\"Calyptia\",lang=\"C\\\"\\\\\\n\",host=\"calyptia.com\",app=\"cmetrics\"} 2 0\n"
+ "cmt_labels_test{dev=\"Calyptia\",lang=\"C\\\"\\\\\\n\",host=\"\\\"calyptia.com\\\"\",app=\"cme\\\\tr\\nics\"} 1 0\n";
+
+ cmt_initialize();
+
+ cmt = cmt_create();
+ TEST_CHECK(cmt != NULL);
+
+ c = cmt_counter_create(cmt, "cmt", "labels", "test", "\"Static\\ labels \ntest",
+ 2, (char *[]) {"host", "app"});
+
+ ts = 0;
+ cmt_counter_inc(c, ts, 0, NULL);
+ cmt_counter_inc(c, ts, 2, (char *[]) {"calyptia.com", "cmetrics"});
+ cmt_counter_inc(c, ts, 2, (char *[]) {"calyptia.com", "cmetrics"});
+ cmt_counter_inc(c, ts, 2, (char *[]) {"\"calyptia.com\"", "cme\\tr\nics"});
+
+ /* Encode to prometheus (no static labels) */
+ text = cmt_encode_prometheus_create(cmt, CMT_TRUE);
+ printf("\n%s\n", text);
+ TEST_CHECK(strcmp(text, out1) == 0);
+ cmt_encode_prometheus_destroy(text);
+
+ /* append static labels */
+ cmt_label_add(cmt, "dev", "Calyptia");
+ cmt_label_add(cmt, "lang", "C\"\\\n");
+
+ text = cmt_encode_prometheus_create(cmt, CMT_TRUE);
+ printf("%s\n", text);
+ TEST_CHECK(strcmp(text, out2) == 0);
+ cmt_encode_prometheus_destroy(text);
+
+ cmt_destroy(cmt);
+}
+
+void test_text()
+{
+ uint64_t ts;
+ cfl_sds_t text;
+ struct cmt *cmt;
+ struct cmt_counter *c;
+
+ char *out1 = \
+ "1970-01-01T00:00:00.000000000Z cmt_labels_test = 1\n"
+ "1970-01-01T00:00:00.000000000Z cmt_labels_test{host=\"calyptia.com\",app=\"cmetrics\"} = 2\n";
+
+ char *out2 = \
+ "1970-01-01T00:00:00.000000000Z cmt_labels_test{dev=\"Calyptia\",lang=\"C\"} = 1\n"
+ "1970-01-01T00:00:00.000000000Z cmt_labels_test{dev=\"Calyptia\",lang=\"C\",host=\"calyptia.com\",app=\"cmetrics\"} = 2\n";
+
+ cmt_initialize();
+
+ cmt = cmt_create();
+ TEST_CHECK(cmt != NULL);
+
+ c = cmt_counter_create(cmt, "cmt", "labels", "test", "Static labels test",
+ 2, (char *[]) {"host", "app"});
+
+ ts = 0;
+ cmt_counter_inc(c, ts, 0, NULL);
+ cmt_counter_inc(c, ts, 2, (char *[]) {"calyptia.com", "cmetrics"});
+ cmt_counter_inc(c, ts, 2, (char *[]) {"calyptia.com", "cmetrics"});
+
+ /* Encode to prometheus (no static labels) */
+ text = cmt_encode_text_create(cmt);
+ printf("\n%s\n", text);
+ TEST_CHECK(strcmp(text, out1) == 0);
+ cmt_encode_text_destroy(text);
+
+ /* append static labels */
+ cmt_label_add(cmt, "dev", "Calyptia");
+ cmt_label_add(cmt, "lang", "C");
+
+ text = cmt_encode_text_create(cmt);
+ printf("%s\n", text);
+ TEST_CHECK(strcmp(text, out2) == 0);
+ cmt_encode_text_destroy(text);
+
+ cmt_destroy(cmt);
+}
+
+void test_influx()
+{
+ uint64_t ts;
+ cfl_sds_t text;
+ struct cmt *cmt;
+ struct cmt_counter *c1;
+ struct cmt_counter *c2;
+
+ char *out1 = \
+ "cmt_labels test=1 1435658235000000123\n"
+ "cmt_labels,host=calyptia.com,app=cmetrics test=2 1435658235000000123\n"
+ "cmt,host=aaa,app=bbb nosubsystem=1 1435658235000000123\n";
+
+ char *out2 = \
+ "cmt_labels,dev=Calyptia,lang=C test=1 1435658235000000123\n"
+ "cmt_labels,dev=Calyptia,lang=C,host=calyptia.com,app=cmetrics test=2 1435658235000000123\n"
+ "cmt,dev=Calyptia,lang=C,host=aaa,app=bbb nosubsystem=1 1435658235000000123\n";
+
+ cmt = cmt_create();
+ TEST_CHECK(cmt != NULL);
+
+ c1 = cmt_counter_create(cmt, "cmt", "labels", "test", "Static labels test",
+ 2, (char *[]) {"host", "app"});
+
+ ts = 1435658235000000123;
+ cmt_counter_inc(c1, ts, 0, NULL);
+ cmt_counter_inc(c1, ts, 2, (char *[]) {"calyptia.com", "cmetrics"});
+ cmt_counter_inc(c1, ts, 2, (char *[]) {"calyptia.com", "cmetrics"});
+
+ c2 = cmt_counter_create(cmt, "cmt", "", "nosubsystem", "No subsystem",
+ 2, (char *[]) {"host", "app"});
+
+ cmt_counter_inc(c2, ts, 2, (char *[]) {"aaa", "bbb"});
+
+ /* Encode to prometheus (no static labels) */
+ text = cmt_encode_influx_create(cmt);
+ printf("%s\n", text);
+ TEST_CHECK(strcmp(text, out1) == 0);
+ cmt_encode_influx_destroy(text);
+
+ /* append static labels */
+ cmt_label_add(cmt, "dev", "Calyptia");
+ cmt_label_add(cmt, "lang", "C");
+
+ text = cmt_encode_influx_create(cmt);
+ printf("%s\n", text);
+ TEST_CHECK(strcmp(text, out2) == 0);
+ cmt_encode_influx_destroy(text);
+
+ cmt_destroy(cmt);
+}
+
+void test_splunk_hec()
+{
+ uint64_t ts;
+ cfl_sds_t text;
+ struct cmt *cmt;
+ struct cmt_counter *c1;
+ struct cmt_counter *c2;
+ const char *host = "localhost", *index = "fluent-bit-metrics", *source = "fluent-bit-cmetrics", *source_type = "cmetrics";
+
+ char *out1 = \
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:labels.test\":1.0}}"
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:labels.test\":2.0,\"host\":\"calyptia.com\",\"app\":\"cmetrics\"}}"
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:nosubsystem\":1.0,\"host\":\"aaa\",\"app\":\"bbb\"}}";
+
+ char *out2 = \
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"fields\":{\"metric_name:labels.test\":1.0,\"dev\":\"Calyptia\",\"lang\":\"C\"}}"
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"fields\":{\"metric_name:labels.test\":2.0,\"dev\":\"Calyptia\",\"lang\":\"C\",\"host\":\"calyptia.com\",\"app\":\"cmetrics\"}}"
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"fields\":{\"metric_name:nosubsystem\":1.0,\"dev\":\"Calyptia\",\"lang\":\"C\",\"host\":\"aaa\",\"app\":\"bbb\"}}";
+
+ cmt = cmt_create();
+ TEST_CHECK(cmt != NULL);
+
+ c1 = cmt_counter_create(cmt, "cmt", "labels", "test", "Static labels test",
+ 2, (char *[]) {"host", "app"});
+
+ ts = 1435658235000000123;
+ cmt_counter_inc(c1, ts, 0, NULL);
+ cmt_counter_inc(c1, ts, 2, (char *[]) {"calyptia.com", "cmetrics"});
+ cmt_counter_inc(c1, ts, 2, (char *[]) {"calyptia.com", "cmetrics"});
+
+ c2 = cmt_counter_create(cmt, "cmt", "", "nosubsystem", "No subsystem",
+ 2, (char *[]) {"host", "app"});
+
+ cmt_counter_inc(c2, ts, 2, (char *[]) {"aaa", "bbb"});
+
+ /* Encode to splunk hec (no static labels) */
+ text = cmt_encode_splunk_hec_create(cmt, host, index, source, source_type);
+ printf("%s\n", text);
+ TEST_CHECK(strcmp(text, out1) == 0);
+ cmt_encode_splunk_hec_destroy(text);
+
+ /* append static labels */
+ cmt_label_add(cmt, "dev", "Calyptia");
+ cmt_label_add(cmt, "lang", "C");
+
+ text = cmt_encode_splunk_hec_create(cmt, host, index, NULL, NULL);
+ printf("%s\n", text);
+ TEST_CHECK(strcmp(text, out2) == 0);
+ cmt_encode_splunk_hec_destroy(text);
+
+ cmt_destroy(cmt);
+}
+
+
+void test_splunk_hec_floating_point()
+{
+ uint64_t ts;
+ cfl_sds_t text;
+ struct cmt *cmt;
+ struct cmt_counter *c1;
+ struct cmt_counter *c2;
+ const char *host = "localhost", *index = "fluent-bit-metrics", *source = "fluent-bit-cmetrics", *source_type = "cmetrics";
+
+ char *out1 = \
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:labels.test\":0.0}}"
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:labels.test\":2.340000e+12,\"host\":\"calyptia.com\",\"app\":\"cmetrics\"}}"
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:nosubsystem\":0.0}}"
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:nosubsystem\":5.000000e+15,\"host\":\"aaa\",\"app\":\"bbb\"}}";
+ char *out2 = \
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"fields\":{\"metric_name:labels.test\":0.0,\"dev\":\"Calyptia\",\"lang\":\"C\"}}"
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"fields\":{\"metric_name:labels.test\":2.340000e+12,\"dev\":\"Calyptia\",\"lang\":\"C\",\"host\":\"calyptia.com\",\"app\":\"cmetrics\"}}"
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"fields\":{\"metric_name:nosubsystem\":0.0,\"dev\":\"Calyptia\",\"lang\":\"C\"}}"
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"fields\":{\"metric_name:nosubsystem\":5.000000e+15,\"dev\":\"Calyptia\",\"lang\":\"C\",\"host\":\"aaa\",\"app\":\"bbb\"}}";
+
+ cmt = cmt_create();
+ TEST_CHECK(cmt != NULL);
+
+ c1 = cmt_counter_create(cmt, "cmt", "labels", "test", "Static labels test",
+ 2, (char *[]) {"host", "app"});
+
+ ts = 1435658235000000123;
+ cmt_counter_set(c1, ts, 0, 0, NULL);
+ cmt_counter_add(c1, ts, 2e+10, 2, (char *[]) {"calyptia.com", "cmetrics"});
+ cmt_counter_add(c1, ts, 2.32e+12, 2, (char *[]) {"calyptia.com", "cmetrics"});
+
+ c2 = cmt_counter_create(cmt, "cmt", "", "nosubsystem", "No subsystem",
+ 2, (char *[]) {"host", "app"});
+
+ cmt_counter_set(c2, ts, 0, 0, NULL);
+ cmt_counter_add(c2, ts, 5e+15, 2, (char *[]) {"aaa", "bbb"});
+
+ /* Encode to splunk hec (no static labels) */
+ text = cmt_encode_splunk_hec_create(cmt, host, index, source, source_type);
+ printf("%s\n", text);
+ TEST_CHECK(strcmp(text, out1) == 0);
+ cmt_encode_splunk_hec_destroy(text);
+
+ /* append static labels */
+ cmt_label_add(cmt, "dev", "Calyptia");
+ cmt_label_add(cmt, "lang", "C");
+
+ text = cmt_encode_splunk_hec_create(cmt, host, index, NULL, NULL);
+ printf("%s\n", text);
+ TEST_CHECK(strcmp(text, out2) == 0);
+ cmt_encode_splunk_hec_destroy(text);
+
+ cmt_destroy(cmt);
+}
+
+/* values to observe in a histogram */
+double hist_observe_values[10] = {
+ 0.0 , 1.02, 2.04, 3.06,
+ 4.08, 5.10, 6.12, 7.14,
+ 8.16, 9.18
+ };
+
+static int histogram_observe_all(struct cmt_histogram *h,
+ uint64_t timestamp,
+ int labels_count, char **labels_vals)
+{
+ int i;
+ double val;
+
+ for (i = 0; i < sizeof(hist_observe_values)/(sizeof(double)); i++) {
+ val = hist_observe_values[i];
+ cmt_histogram_observe(h, timestamp, val, labels_count, labels_vals);
+ }
+
+ return i;
+}
+
+void test_splunk_hec_histogram()
+{
+ uint64_t ts;
+ cfl_sds_t text;
+ struct cmt *cmt;
+ struct cmt_histogram *h;
+ struct cmt_histogram_buckets *buckets;
+ const char *host = "localhost", *index = "fluent-bit-metrics", *source = "fluent-bit-cmetrics", *source_type = "cmetrics";
+
+ char *out1 =
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:network.load_bucket\":1.0,\"le\":\"0.005\",\"metric_type\":\"Histogram\"}}"
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:network.load_bucket\":1.0,\"le\":\"0.01\",\"metric_type\":\"Histogram\"}}"
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:network.load_bucket\":1.0,\"le\":\"0.025\",\"metric_type\":\"Histogram\"}}"
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:network.load_bucket\":1.0,\"le\":\"0.05\",\"metric_type\":\"Histogram\"}}"
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:network.load_bucket\":1.0,\"le\":\"0.1\",\"metric_type\":\"Histogram\"}}"
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:network.load_bucket\":1.0,\"le\":\"0.25\",\"metric_type\":\"Histogram\"}}"
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:network.load_bucket\":1.0,\"le\":\"0.5\",\"metric_type\":\"Histogram\"}}"
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:network.load_bucket\":1.0,\"le\":\"1.0\",\"metric_type\":\"Histogram\"}}"
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:network.load_bucket\":3.0,\"le\":\"2.5\",\"metric_type\":\"Histogram\"}}"
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:network.load_bucket\":5.0,\"le\":\"5.0\",\"metric_type\":\"Histogram\"}}"
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:network.load_bucket\":10.0,\"le\":\"10.0\",\"metric_type\":\"Histogram\"}}"
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:network.load_bucket\":10.0,\"le\":\"+Inf\",\"metric_type\":\"Histogram\"}}"
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:network.load_sum\":45.0,\"metric_type\":\"Histogram\"}}"
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:network.load_count\":10.0,\"metric_type\":\"Histogram\"}}";
+ char *out2 =
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:network.load_bucket\":1.0,\"le\":\"0.005\",\"static\":\"test\",\"metric_type\":\"Histogram\"}}"
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:network.load_bucket\":1.0,\"le\":\"0.01\",\"static\":\"test\",\"metric_type\":\"Histogram\"}}"
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:network.load_bucket\":1.0,\"le\":\"0.025\",\"static\":\"test\",\"metric_type\":\"Histogram\"}}"
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:network.load_bucket\":1.0,\"le\":\"0.05\",\"static\":\"test\",\"metric_type\":\"Histogram\"}}"
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:network.load_bucket\":1.0,\"le\":\"0.1\",\"static\":\"test\",\"metric_type\":\"Histogram\"}}"
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:network.load_bucket\":1.0,\"le\":\"0.25\",\"static\":\"test\",\"metric_type\":\"Histogram\"}}"
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:network.load_bucket\":1.0,\"le\":\"0.5\",\"static\":\"test\",\"metric_type\":\"Histogram\"}}"
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:network.load_bucket\":1.0,\"le\":\"1.0\",\"static\":\"test\",\"metric_type\":\"Histogram\"}}"
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:network.load_bucket\":3.0,\"le\":\"2.5\",\"static\":\"test\",\"metric_type\":\"Histogram\"}}"
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:network.load_bucket\":5.0,\"le\":\"5.0\",\"static\":\"test\",\"metric_type\":\"Histogram\"}}"
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:network.load_bucket\":10.0,\"le\":\"10.0\",\"static\":\"test\",\"metric_type\":\"Histogram\"}}"
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:network.load_bucket\":10.0,\"le\":\"+Inf\",\"static\":\"test\",\"metric_type\":\"Histogram\"}}"
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:network.load_sum\":45.0,\"static\":\"test\",\"metric_type\":\"Histogram\"}}"
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:network.load_count\":10.0,\"static\":\"test\",\"metric_type\":\"Histogram\"}}";
+
+ cmt_initialize();
+
+ /* CMetrics context */
+ cmt = cmt_create();
+ TEST_CHECK(cmt != NULL);
+
+ /* Timestamp */
+ ts = 1435658235000000123;
+
+ /* Create buckets */
+ buckets = cmt_histogram_buckets_create(11,
+ 0.005, 0.01, 0.025, 0.05,
+ 0.1, 0.25, 0.5, 1.0, 2.5,
+ 5.0, 10.0);
+ TEST_CHECK(buckets != NULL);
+
+ /* Create a gauge metric type */
+ h = cmt_histogram_create(cmt,
+ "k8s", "network", "load", "Network load",
+ buckets,
+ 1, (char *[]) {"my_label"});
+ TEST_CHECK(h != NULL);
+
+ /* no labels */
+ histogram_observe_all(h, ts, 0, NULL);
+ text = cmt_encode_splunk_hec_create(cmt, host, index, source, source_type);
+ printf("%s\n", text);
+ TEST_CHECK(strcmp(text, out1) == 0);
+ cmt_encode_splunk_hec_destroy(text);
+
+ /* static label: register static label for the context */
+ cmt_label_add(cmt, "static", "test");
+ text = cmt_encode_splunk_hec_create(cmt, host, index, source, source_type);
+ printf("%s\n", text);
+ TEST_CHECK(strcmp(text, out2) == 0);
+ cmt_encode_splunk_hec_destroy(text);
+
+ /* defined labels: add a custom label value */
+ histogram_observe_all(h, ts, 1, (char *[]) {"val"});
+ text = cmt_encode_splunk_hec_create(cmt, host, index, source, source_type);
+ printf("%s\n", text);
+ cmt_encode_splunk_hec_destroy(text);
+
+ cmt_destroy(cmt);
+}
+
+void test_splunk_hec_summary()
+{
+ double sum;
+ uint64_t count;
+ uint64_t ts;
+ double q[6];
+ double r[6];
+ cfl_sds_t text;
+ struct cmt *cmt;
+ struct cmt_summary *s;
+ const char *host = "localhost", *index = "fluent-bit-metrics", *source = "fluent-bit-cmetrics", *source_type = "cmetrics";
+
+ char *out1 =
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:network.load_sum\":51.0,\"metric_type\":\"Summary\"}}"
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:network.load_count\":10.0,\"metric_type\":\"Summary\"}}";
+ char *out2 =
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:network.load\":1.0,\"qt\":\"0.1\",\"metric_type\":\"Summary\"}}"
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:network.load\":2.0,\"qt\":\"0.2\",\"metric_type\":\"Summary\"}}"
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:network.load\":3.0,\"qt\":\"0.3\",\"metric_type\":\"Summary\"}}"
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:network.load\":4.0,\"qt\":\"0.4\",\"metric_type\":\"Summary\"}}"
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:network.load\":5.0,\"qt\":\"0.5\",\"metric_type\":\"Summary\"}}"
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:network.load\":6.0,\"qt\":\"1.0\",\"metric_type\":\"Summary\"}}"
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:network.load_sum\":51.0,\"metric_type\":\"Summary\"}}"
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:network.load_count\":10.0,\"metric_type\":\"Summary\"}}";
+ char *out3 =
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:network.load\":1.0,\"qt\":\"0.1\",\"static\":\"test\",\"metric_type\":\"Summary\"}}"
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:network.load\":2.0,\"qt\":\"0.2\",\"static\":\"test\",\"metric_type\":\"Summary\"}}"
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:network.load\":3.0,\"qt\":\"0.3\",\"static\":\"test\",\"metric_type\":\"Summary\"}}"
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:network.load\":4.0,\"qt\":\"0.4\",\"static\":\"test\",\"metric_type\":\"Summary\"}}"
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:network.load\":5.0,\"qt\":\"0.5\",\"static\":\"test\",\"metric_type\":\"Summary\"}}"
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:network.load\":6.0,\"qt\":\"1.0\",\"static\":\"test\",\"metric_type\":\"Summary\"}}"
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:network.load_sum\":51.0,\"static\":\"test\",\"metric_type\":\"Summary\"}}"
+ "{\"host\":\"localhost\",\"time\":1435658235.000000123,\"event\":\"metric\",\"index\":\"fluent-bit-metrics\",\"source\":\"fluent-bit-cmetrics\",\"sourcetype\":\"cmetrics\",\"fields\":{\"metric_name:network.load_count\":10.0,\"static\":\"test\",\"metric_type\":\"Summary\"}}";
+
+ cmt_initialize();
+
+ /* Timestamp */
+ ts = 1435658235000000123;
+
+ /* CMetrics context */
+ cmt = cmt_create();
+ TEST_CHECK(cmt != NULL);
+
+ /* set quantiles, no labels */
+ q[0] = 0.1;
+ q[1] = 0.2;
+ q[2] = 0.3;
+ q[3] = 0.4;
+ q[4] = 0.5;
+ q[5] = 1.0;
+
+ r[0] = 1;
+ r[1] = 2;
+ r[2] = 3;
+ r[3] = 4;
+ r[4] = 5;
+ r[5] = 6;
+
+ /* Create a gauge metric type */
+ s = cmt_summary_create(cmt,
+ "k8s", "network", "load", "Network load",
+ 6, q,
+ 1, (char *[]) {"my_label"});
+ TEST_CHECK(s != NULL);
+
+ count = 10;
+ sum = 51.612894511314444;
+
+ /* no quantiles, no labels */
+ cmt_summary_set_default(s, ts, NULL, sum, count, 0, NULL);
+ text = cmt_encode_splunk_hec_create(cmt, host, index, source, source_type);
+ printf("%s\n", text);
+ TEST_CHECK(strcmp(text, out1) == 0);
+ cmt_encode_splunk_hec_destroy(text);
+
+ cmt_summary_set_default(s, ts, r, sum, count, 0, NULL);
+ text = cmt_encode_splunk_hec_create(cmt, host, index, source, source_type);
+ printf("%s\n", text);
+ TEST_CHECK(strcmp(text, out2) == 0);
+ cmt_encode_splunk_hec_destroy(text);
+
+ /* static label: register static label for the context */
+ cmt_label_add(cmt, "static", "test");
+ text = cmt_encode_splunk_hec_create(cmt, host, index, source, source_type);
+ printf("%s\n", text);
+ TEST_CHECK(strcmp(text, out3) == 0);
+ cmt_encode_splunk_hec_destroy(text);
+
+ cmt_destroy(cmt);
+}
+
+TEST_LIST = {
+ {"cmt_msgpack_cleanup_on_error", test_cmt_to_msgpack_cleanup_on_error},
+ {"cmt_msgpack_partial_processing", test_cmt_msgpack_partial_processing},
+ {"prometheus_remote_write", test_prometheus_remote_write},
+ {"cmt_msgpack_stability", test_cmt_to_msgpack_stability},
+ {"cmt_msgpack_integrity", test_cmt_to_msgpack_integrity},
+ {"cmt_msgpack_labels", test_cmt_to_msgpack_labels},
+ {"cmt_msgpack", test_cmt_to_msgpack},
+ {"opentelemetry", test_opentelemetry},
+ {"prometheus", test_prometheus},
+ {"text", test_text},
+ {"influx", test_influx},
+ {"splunk_hec", test_splunk_hec},
+ {"splunk_hec_floating_point", test_splunk_hec_floating_point},
+ {"splunk_hec_histogram", test_splunk_hec_histogram},
+ {"splunk_hec_summary", test_splunk_hec_summary},
+ { 0 }
+};
diff --git a/src/fluent-bit/lib/cmetrics/tests/gauge.c b/src/fluent-bit/lib/cmetrics/tests/gauge.c
new file mode 100644
index 000000000..3c2196010
--- /dev/null
+++ b/src/fluent-bit/lib/cmetrics/tests/gauge.c
@@ -0,0 +1,174 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* CMetrics
+ * ========
+ * Copyright 2021-2022 The CMetrics Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cmetrics/cmetrics.h>
+#include <cmetrics/cmt_gauge.h>
+#include <cmetrics/cmt_encode_prometheus.h>
+
+#include "cmt_tests.h"
+
+void test_gauge()
+{
+ int ret;
+ double val;
+ uint64_t ts;
+ struct cmt *cmt;
+ struct cmt_gauge *g;
+
+ cmt_initialize();
+
+ cmt = cmt_create();
+ TEST_CHECK(cmt != NULL);
+
+ /* Create a gauge metric type */
+ g = cmt_gauge_create(cmt, "kubernetes", "network", "load", "Network load", 0, NULL);
+ TEST_CHECK(g != NULL);
+
+ /* Timestamp */
+ ts = cfl_time_now();
+
+ /* Default value */
+ ret = cmt_gauge_get_val(g, 0, NULL, &val);
+ TEST_CHECK(val == 0.0);
+
+ /* Set a value of two */
+ cmt_gauge_set(g, ts, 2.0, 0, NULL);
+ ret = cmt_gauge_get_val(g, 0, NULL, &val);
+ TEST_CHECK(ret == 0);
+ TEST_CHECK(val == 2.0);
+
+ /* Increment one */
+ cmt_gauge_inc(g, ts, 0, NULL);
+ ret = cmt_gauge_get_val(g, 0, NULL, &val);
+ TEST_CHECK(ret == 0);
+ TEST_CHECK(val == 3.0);
+
+ /* Substract 2 */
+ ret = cmt_gauge_sub(g, ts, 2, 0, NULL);
+ TEST_CHECK(ret == 0);
+
+ ret = cmt_gauge_get_val(g, 0, NULL, &val);
+ TEST_CHECK(ret == 0);
+ TEST_CHECK(val == 1.0);
+
+ /* Decrement by one */
+ ret = cmt_gauge_dec(g, ts, 0, NULL);
+ TEST_CHECK(ret == 0);
+
+ ret = cmt_gauge_get_val(g, 0, NULL, &val);
+ TEST_CHECK(ret == 0);
+ TEST_CHECK(val == 0.0);
+
+ cmt_destroy(cmt);
+}
+
+void test_labels()
+{
+ int ret;
+ double val;
+ uint64_t ts;
+ cfl_sds_t prom;
+ struct cmt *cmt;
+ struct cmt_gauge *g;
+
+ cmt_initialize();
+
+ cmt = cmt_create();
+ TEST_CHECK(cmt != NULL);
+
+ /* Create a counter metric type */
+ g = cmt_gauge_create(cmt, "kubernetes", "network", "load", "Network load",
+ 2, (char *[]) {"hostname", "app"});
+ TEST_CHECK(g != NULL);
+
+ /* Timestamp */
+ ts = cfl_time_now();
+
+ /*
+ * Test 1: hash zero (no labels)
+ * -----------------------------
+ */
+
+ /* Default value for hash zero */
+ ret = cmt_gauge_get_val(g, 0, NULL, &val);
+ TEST_CHECK(ret == -1);
+
+ /* Increment hash zero by 1 */
+ ret = cmt_gauge_inc(g, ts, 0, NULL);
+ TEST_CHECK(ret == 0);
+
+ /* Check the new value */
+ ret = cmt_gauge_get_val(g, 0, NULL, &val);
+ TEST_CHECK(ret == 0);
+ TEST_CHECK(val == 1.0);
+
+ /* Add two */
+ ret = cmt_gauge_add(g, ts, 2, 0, NULL);
+ TEST_CHECK(ret == 0);
+
+ /* Check that hash zero val is 3.0 */
+ ret = cmt_gauge_get_val(g, 0, NULL, &val);
+ TEST_CHECK(ret == 0);
+ TEST_CHECK(val == 3.0);
+
+ /*
+ * Test 2: custom labels
+ * ---------------------
+ */
+
+ /* Increment custom metric */
+ ret = cmt_gauge_inc(g, ts, 2, (char *[]) {"localhost", "cmetrics"});
+ TEST_CHECK(ret == 0);
+
+ /* Check ret = 1 */
+ ret = cmt_gauge_get_val(g, 2, (char *[]) {"localhost", "cmetrics"}, &val);
+ TEST_CHECK(ret == 0);
+ TEST_CHECK(val == 1.000);
+
+ /* Add 10 to another metric using a different second label */
+ ret = cmt_gauge_add(g, ts, 10, 2, (char *[]) {"localhost", "test"});
+ TEST_CHECK(ret == 0);
+
+ /* Validate the value */
+ ret = cmt_gauge_get_val(g, 2, (char *[]) {"localhost", "test"}, &val);
+ TEST_CHECK(ret == 0);
+ TEST_CHECK(val == 10.00);
+
+ /* Substract two */
+ ret = cmt_gauge_sub(g, ts, 2.5, 2, (char *[]) {"localhost", "test"});
+ TEST_CHECK(ret == 0);
+
+ /* Validate the value */
+ ret = cmt_gauge_get_val(g, 2, (char *[]) {"localhost", "test"}, &val);
+ TEST_CHECK(ret == 0);
+ TEST_CHECK(val == 7.50);
+
+ printf("\n");
+ prom = cmt_encode_prometheus_create(cmt, CMT_TRUE);
+ printf("%s\n", prom);
+ cmt_encode_prometheus_destroy(prom);
+
+ cmt_destroy(cmt);
+}
+
+TEST_LIST = {
+ {"basic" , test_gauge},
+ {"labels", test_labels},
+ { 0 }
+};
diff --git a/src/fluent-bit/lib/cmetrics/tests/histogram.c b/src/fluent-bit/lib/cmetrics/tests/histogram.c
new file mode 100644
index 000000000..d5fc80bbb
--- /dev/null
+++ b/src/fluent-bit/lib/cmetrics/tests/histogram.c
@@ -0,0 +1,212 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* CMetrics
+ * ========
+ * Copyright 2021-2022 The CMetrics Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cmetrics/cmetrics.h>
+#include <cmetrics/cmt_histogram.h>
+#include <cmetrics/cmt_encode_prometheus.h>
+#include <cmetrics/cmt_map.h>
+#include "cmt_tests.h"
+
+#include <math.h>
+#include <float.h>
+#include <stdbool.h>
+
+/* values to observe in a histogram */
+double hist_observe_values[10] = {
+ 0.0 , 1.02, 2.04, 3.06,
+ 4.08, 5.10, 6.12, 7.14,
+ 8.16, 9.18
+ };
+
+/*
+ * histogram bucket values: the values computed in the buckets,
+ * all of them are uint64_t.
+ *
+ * Note that on all examples we use the default buckets values, created manually
+ * and through the API:
+ *
+ * - 11 bucket values
+ * - 1 +Inf bucket value
+ */
+uint64_t hist_buckets_values[12] = {1, 1, 1, 1, 1, 1, 1, 1,
+ 3, 5, 10, 10};
+
+/* histogram _count value */
+uint64_t hist_count = 10;
+
+/* histogram _sum value */
+double hist_sum = 45.9;
+
+bool fequal(double a, double b)
+{
+ return (fabs(a - b) < (DBL_EPSILON * fabs(a + b)));
+}
+
+static void histogram_check(struct cmt_histogram *h,
+ int labels_count, char **labels_vals)
+{
+ int i;
+ int ret;
+ uint64_t val;
+ struct cmt_metric *metric;
+
+ /* retrieve the metric context */
+ metric = cmt_map_metric_get(&h->opts, h->map,
+ labels_count, labels_vals, CMT_TRUE);
+ TEST_CHECK(metric != NULL);
+
+ /* check bucket values */
+ for (i = 0; i < (sizeof(hist_buckets_values)/sizeof(uint64_t)); i++) {
+ val = cmt_metric_hist_get_value(metric, i);
+ TEST_CHECK(val == hist_buckets_values[i]);
+ }
+
+ /* check _count */
+ TEST_CHECK(hist_count == cmt_metric_hist_get_count_value(metric));
+
+ /* check _sum */
+ ret = fequal(hist_sum, cmt_metric_hist_get_sum_value(metric));
+ TEST_CHECK(ret != 0);
+}
+
+static int histogram_observe_all(struct cmt_histogram *h,
+ uint64_t timestamp,
+ int labels_count, char **labels_vals)
+{
+ int i;
+ double val;
+
+ for (i = 0; i < sizeof(hist_observe_values)/(sizeof(double)); i++) {
+ val = hist_observe_values[i];
+ cmt_histogram_observe(h, timestamp, val, labels_count, labels_vals);
+ }
+
+ return i;
+}
+
+static void prometheus_encode_test(struct cmt *cmt)
+{
+ cfl_sds_t buf;
+
+ buf = cmt_encode_prometheus_create(cmt, CMT_FALSE);
+ printf("\n%s\n", buf);
+ cmt_encode_prometheus_destroy(buf);
+}
+
+
+void test_histogram()
+{
+ uint64_t ts;
+ struct cmt *cmt;
+ struct cmt_histogram *h;
+ struct cmt_histogram_buckets *buckets;
+
+ cmt_initialize();
+
+ /* Timestamp */
+ ts = cfl_time_now();
+
+ /* CMetrics context */
+ cmt = cmt_create();
+ TEST_CHECK(cmt != NULL);
+
+ /* Create buckets */
+ buckets = cmt_histogram_buckets_create(11,
+ 0.005, 0.01, 0.025, 0.05,
+ 0.1, 0.25, 0.5, 1.0, 2.5,
+ 5.0, 10.0);
+ TEST_CHECK(buckets != NULL);
+
+ /* Create a gauge metric type */
+ h = cmt_histogram_create(cmt,
+ "k8s", "network", "load", "Network load",
+ buckets,
+ 1, (char *[]) {"my_label"});
+ TEST_CHECK(h != NULL);
+
+ /* no labels */
+ histogram_observe_all(h, ts, 0, NULL);
+ histogram_check(h, 0, NULL);
+ prometheus_encode_test(cmt);
+
+ /* static label: register static label for the context */
+ cmt_label_add(cmt, "static", "test");
+ histogram_check(h, 0, NULL);
+ prometheus_encode_test(cmt);
+
+ /* defined labels: add a custom label value */
+ histogram_observe_all(h, ts, 1, (char *[]) {"val"});
+ histogram_check(h, 1, (char *[]) {"val"});
+ prometheus_encode_test(cmt);
+
+ cmt_destroy(cmt);
+}
+
+void test_set_defaults()
+{
+ uint64_t ts;
+ struct cmt *cmt;
+ struct cmt_histogram *h;
+ struct cmt_histogram_buckets *buckets;
+
+ cmt_initialize();
+
+ /* Timestamp */
+ ts = cfl_time_now();
+
+ /* CMetrics context */
+ cmt = cmt_create();
+ TEST_CHECK(cmt != NULL);
+
+ /* Create buckets */
+ buckets = cmt_histogram_buckets_default_create();
+ TEST_CHECK(buckets != NULL);
+
+ /* Create a gauge metric type */
+ h = cmt_histogram_create(cmt,
+ "k8s", "network", "load", "Network load",
+ buckets,
+ 1, (char *[]) {"my_label"});
+ TEST_CHECK(h != NULL);
+
+ /* set default buckets values / no labels */
+ cmt_histogram_set_default(h, ts,
+ hist_buckets_values,
+ hist_sum, hist_count, 0, NULL);
+ histogram_check(h, 0, NULL);
+ prometheus_encode_test(cmt);
+
+ /* static label: register static label for the context */
+ cmt_label_add(cmt, "static", "test");
+ histogram_check(h, 0, NULL);
+ prometheus_encode_test(cmt);
+
+ /* perform observation with labels */
+ histogram_observe_all(h, ts, 1, (char *[]) {"val"});
+ histogram_check(h, 1, (char *[]) {"val"});
+ prometheus_encode_test(cmt);
+
+ cmt_destroy(cmt);
+}
+
+TEST_LIST = {
+ {"histogram" , test_histogram},
+ {"set_defaults", test_set_defaults},
+ { 0 }
+};
diff --git a/src/fluent-bit/lib/cmetrics/tests/issues.c b/src/fluent-bit/lib/cmetrics/tests/issues.c
new file mode 100644
index 000000000..8e6116e26
--- /dev/null
+++ b/src/fluent-bit/lib/cmetrics/tests/issues.c
@@ -0,0 +1,95 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* CMetrics
+ * ========
+ * Copyright 2021-2022 The CMetrics Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cmetrics/cmetrics.h>
+#include <cmetrics/cmt_counter.h>
+#include <cmetrics/cmt_encode_msgpack.h>
+#include <cmetrics/cmt_decode_msgpack.h>
+#include <cmetrics/cmt_encode_text.h>
+
+#include "cmt_tests.h"
+
+static struct cmt *generate_encoder_test_data()
+{
+ uint64_t ts;
+ struct cmt *cmt;
+ struct cmt_counter *c1;
+ struct cmt_counter *c2;
+
+ ts = 0;
+ cmt = cmt_create();
+
+ c1 = cmt_counter_create(cmt, "kubernetes", "", "load", "Network load",
+ 2, (char *[]) {"hostname", "app"});
+ cmt_counter_set(c1, ts, 10, 0, NULL);
+
+ c2 = cmt_counter_create(cmt, "kubernetes", "", "cpu", "CPU load",
+ 2, (char *[]) {"hostname", "app"});
+ cmt_counter_set(c2, ts, 10, 0, NULL);
+
+ return cmt;
+}
+
+
+void test_issue_54()
+{
+ const char expected_text[] = "1970-01-01T00:00:00.000000000Z kubernetes_load{tag1=\"tag1\",tag2=\"tag2\"} = 10\n" \
+ "1970-01-01T00:00:00.000000000Z kubernetes_cpu{tag1=\"tag1\",tag2=\"tag2\"} = 10\n";
+ cfl_sds_t text_result;
+ size_t mp1_size;
+ char *mp1_buf;
+ size_t offset;
+ int result;
+ struct cmt *cmt2;
+ struct cmt *cmt1;
+
+ cmt_initialize();
+
+ /* Generate context with data */
+ cmt1 = generate_encoder_test_data();
+ TEST_CHECK(NULL != cmt1);
+
+ /* append static labels */
+ cmt_label_add(cmt1, "tag1", "tag1");
+ cmt_label_add(cmt1, "tag2", "tag2");
+
+ /* CMT1 -> Msgpack */
+ result = cmt_encode_msgpack_create(cmt1, &mp1_buf, &mp1_size);
+ TEST_CHECK(0 == result);
+
+ /* Msgpack -> CMT2 */
+ offset = 0;
+ result = cmt_decode_msgpack_create(&cmt2, mp1_buf, mp1_size, &offset);
+ TEST_CHECK(0 == result);
+
+ text_result = cmt_encode_text_create(cmt2);
+
+ TEST_CHECK(NULL != text_result);
+ TEST_CHECK(0 == strcmp(text_result, expected_text));
+
+ cmt_encode_text_destroy(text_result);
+ cmt_decode_msgpack_destroy(cmt2);
+ cmt_encode_msgpack_destroy(mp1_buf);
+ cmt_destroy(cmt1);
+}
+
+TEST_LIST = {
+ {"issue_54", test_issue_54},
+ { 0 }
+};
diff --git a/src/fluent-bit/lib/cmetrics/tests/lib/acutest/acutest.h b/src/fluent-bit/lib/cmetrics/tests/lib/acutest/acutest.h
new file mode 100644
index 000000000..1d13044f0
--- /dev/null
+++ b/src/fluent-bit/lib/cmetrics/tests/lib/acutest/acutest.h
@@ -0,0 +1,1794 @@
+/*
+ * Acutest -- Another C/C++ Unit Test facility
+ * <https://github.com/mity/acutest>
+ *
+ * Copyright 2013-2020 Martin Mitas
+ * Copyright 2019 Garrett D'Amore
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#ifndef ACUTEST_H
+#define ACUTEST_H
+
+
+/************************
+ *** Public interface ***
+ ************************/
+
+/* By default, "acutest.h" provides the main program entry point (function
+ * main()). However, if the test suite is composed of multiple source files
+ * which include "acutest.h", then this causes a problem of multiple main()
+ * definitions. To avoid this problem, #define macro TEST_NO_MAIN in all
+ * compilation units but one.
+ */
+
+/* Macro to specify list of unit tests in the suite.
+ * The unit test implementation MUST provide list of unit tests it implements
+ * with this macro:
+ *
+ * TEST_LIST = {
+ * { "test1_name", test1_func_ptr },
+ * { "test2_name", test2_func_ptr },
+ * ...
+ * { NULL, NULL } // zeroed record marking the end of the list
+ * };
+ *
+ * The list specifies names of each test (must be unique) and pointer to
+ * a function implementing it. The function does not take any arguments
+ * and has no return values, i.e. every test function has to be compatible
+ * with this prototype:
+ *
+ * void test_func(void);
+ *
+ * Note the list has to be ended with a zeroed record.
+ */
+#define TEST_LIST const struct acutest_test_ acutest_list_[]
+
+
+/* Macros for testing whether an unit test succeeds or fails. These macros
+ * can be used arbitrarily in functions implementing the unit tests.
+ *
+ * If any condition fails throughout execution of a test, the test fails.
+ *
+ * TEST_CHECK takes only one argument (the condition), TEST_CHECK_ allows
+ * also to specify an error message to print out if the condition fails.
+ * (It expects printf-like format string and its parameters). The macros
+ * return non-zero (condition passes) or 0 (condition fails).
+ *
+ * That can be useful when more conditions should be checked only if some
+ * preceding condition passes, as illustrated in this code snippet:
+ *
+ * SomeStruct* ptr = allocate_some_struct();
+ * if(TEST_CHECK(ptr != NULL)) {
+ * TEST_CHECK(ptr->member1 < 100);
+ * TEST_CHECK(ptr->member2 > 200);
+ * }
+ */
+#define TEST_CHECK_(cond,...) acutest_check_((cond), __FILE__, __LINE__, __VA_ARGS__)
+#define TEST_CHECK(cond) acutest_check_((cond), __FILE__, __LINE__, "%s", #cond)
+
+
+/* These macros are the same as TEST_CHECK_ and TEST_CHECK except that if the
+ * condition fails, the currently executed unit test is immediately aborted.
+ *
+ * That is done either by calling abort() if the unit test is executed as a
+ * child process; or via longjmp() if the unit test is executed within the
+ * main Acutest process.
+ *
+ * As a side effect of such abortion, your unit tests may cause memory leaks,
+ * unflushed file descriptors, and other phenomena caused by the abortion.
+ *
+ * Therefore you should not use these as a general replacement for TEST_CHECK.
+ * Use it with some caution, especially if your test causes some other side
+ * effects to the outside world (e.g. communicating with some server, inserting
+ * into a database etc.).
+ */
+#define TEST_ASSERT_(cond,...) \
+ do { \
+ if(!acutest_check_((cond), __FILE__, __LINE__, __VA_ARGS__)) \
+ acutest_abort_(); \
+ } while(0)
+#define TEST_ASSERT(cond) \
+ do { \
+ if(!acutest_check_((cond), __FILE__, __LINE__, "%s", #cond)) \
+ acutest_abort_(); \
+ } while(0)
+
+
+#ifdef __cplusplus
+/* Macros to verify that the code (the 1st argument) throws exception of given
+ * type (the 2nd argument). (Note these macros are only available in C++.)
+ *
+ * TEST_EXCEPTION_ is like TEST_EXCEPTION but accepts custom printf-like
+ * message.
+ *
+ * For example:
+ *
+ * TEST_EXCEPTION(function_that_throw(), ExpectedExceptionType);
+ *
+ * If the function_that_throw() throws ExpectedExceptionType, the check passes.
+ * If the function throws anything incompatible with ExpectedExceptionType
+ * (or if it does not thrown an exception at all), the check fails.
+ */
+#define TEST_EXCEPTION(code, exctype) \
+ do { \
+ bool exc_ok_ = false; \
+ const char *msg_ = NULL; \
+ try { \
+ code; \
+ msg_ = "No exception thrown."; \
+ } catch(exctype const&) { \
+ exc_ok_= true; \
+ } catch(...) { \
+ msg_ = "Unexpected exception thrown."; \
+ } \
+ acutest_check_(exc_ok_, __FILE__, __LINE__, #code " throws " #exctype);\
+ if(msg_ != NULL) \
+ acutest_message_("%s", msg_); \
+ } while(0)
+#define TEST_EXCEPTION_(code, exctype, ...) \
+ do { \
+ bool exc_ok_ = false; \
+ const char *msg_ = NULL; \
+ try { \
+ code; \
+ msg_ = "No exception thrown."; \
+ } catch(exctype const&) { \
+ exc_ok_= true; \
+ } catch(...) { \
+ msg_ = "Unexpected exception thrown."; \
+ } \
+ acutest_check_(exc_ok_, __FILE__, __LINE__, __VA_ARGS__); \
+ if(msg_ != NULL) \
+ acutest_message_("%s", msg_); \
+ } while(0)
+#endif /* #ifdef __cplusplus */
+
+
+/* Sometimes it is useful to split execution of more complex unit tests to some
+ * smaller parts and associate those parts with some names.
+ *
+ * This is especially handy if the given unit test is implemented as a loop
+ * over some vector of multiple testing inputs. Using these macros allow to use
+ * sort of subtitle for each iteration of the loop (e.g. outputting the input
+ * itself or a name associated to it), so that if any TEST_CHECK condition
+ * fails in the loop, it can be easily seen which iteration triggers the
+ * failure, without the need to manually output the iteration-specific data in
+ * every single TEST_CHECK inside the loop body.
+ *
+ * TEST_CASE allows to specify only single string as the name of the case,
+ * TEST_CASE_ provides all the power of printf-like string formatting.
+ *
+ * Note that the test cases cannot be nested. Starting a new test case ends
+ * implicitly the previous one. To end the test case explicitly (e.g. to end
+ * the last test case after exiting the loop), you may use TEST_CASE(NULL).
+ */
+#define TEST_CASE_(...) acutest_case_(__VA_ARGS__)
+#define TEST_CASE(name) acutest_case_("%s", name)
+
+
+/* Maximal output per TEST_CASE call. Longer messages are cut.
+ * You may define another limit prior including "acutest.h"
+ */
+#ifndef TEST_CASE_MAXSIZE
+ #define TEST_CASE_MAXSIZE 64
+#endif
+
+
+/* printf-like macro for outputting an extra information about a failure.
+ *
+ * Intended use is to output some computed output versus the expected value,
+ * e.g. like this:
+ *
+ * if(!TEST_CHECK(produced == expected)) {
+ * TEST_MSG("Expected: %d", expected);
+ * TEST_MSG("Produced: %d", produced);
+ * }
+ *
+ * Note the message is only written down if the most recent use of any checking
+ * macro (like e.g. TEST_CHECK or TEST_EXCEPTION) in the current test failed.
+ * This means the above is equivalent to just this:
+ *
+ * TEST_CHECK(produced == expected);
+ * TEST_MSG("Expected: %d", expected);
+ * TEST_MSG("Produced: %d", produced);
+ *
+ * The macro can deal with multi-line output fairly well. It also automatically
+ * adds a final new-line if there is none present.
+ */
+#define TEST_MSG(...) acutest_message_(__VA_ARGS__)
+
+
+/* Maximal output per TEST_MSG call. Longer messages are cut.
+ * You may define another limit prior including "acutest.h"
+ */
+#ifndef TEST_MSG_MAXSIZE
+ #define TEST_MSG_MAXSIZE 1024
+#endif
+
+
+/* Macro for dumping a block of memory.
+ *
+ * Its intended use is very similar to what TEST_MSG is for, but instead of
+ * generating any printf-like message, this is for dumping raw block of a
+ * memory in a hexadecimal form:
+ *
+ * TEST_CHECK(size_produced == size_expected &&
+ * memcmp(addr_produced, addr_expected, size_produced) == 0);
+ * TEST_DUMP("Expected:", addr_expected, size_expected);
+ * TEST_DUMP("Produced:", addr_produced, size_produced);
+ */
+#define TEST_DUMP(title, addr, size) acutest_dump_(title, addr, size)
+
+/* Maximal output per TEST_DUMP call (in bytes to dump). Longer blocks are cut.
+ * You may define another limit prior including "acutest.h"
+ */
+#ifndef TEST_DUMP_MAXSIZE
+ #define TEST_DUMP_MAXSIZE 1024
+#endif
+
+
+/* Common test initialiation/clean-up
+ *
+ * In some test suites, it may be needed to perform some sort of the same
+ * initialization and/or clean-up in all the tests.
+ *
+ * Such test suites may use macros TEST_INIT and/or TEST_FINI prior including
+ * this header. The expansion of the macro is then used as a body of helper
+ * function called just before executing every single (TEST_INIT) or just after
+ * it ends (TEST_FINI).
+ *
+ * Examples of various ways how to use the macro TEST_INIT:
+ *
+ * #define TEST_INIT my_init_func();
+ * #define TEST_INIT my_init_func() // Works even without the semicolon
+ * #define TEST_INIT setlocale(LC_ALL, NULL);
+ * #define TEST_INIT { setlocale(LC_ALL, NULL); my_init_func(); }
+ *
+ * TEST_FINI is to be used in the same way.
+ */
+
+
+/**********************
+ *** Implementation ***
+ **********************/
+
+/* The unit test files should not rely on anything below. */
+
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <setjmp.h>
+
+#if defined(unix) || defined(__unix__) || defined(__unix) || defined(__APPLE__)
+ #define ACUTEST_UNIX_ 1
+ #include <errno.h>
+ #include <libgen.h>
+ #include <unistd.h>
+ #include <sys/types.h>
+ #include <sys/wait.h>
+ #include <signal.h>
+ #include <time.h>
+
+ #if defined CLOCK_PROCESS_CPUTIME_ID && defined CLOCK_MONOTONIC
+ #define ACUTEST_HAS_POSIX_TIMER_ 1
+ #endif
+#endif
+
+#if defined(_gnu_linux_) || defined(__linux__)
+ #define ACUTEST_LINUX_ 1
+ #include <fcntl.h>
+ #include <sys/stat.h>
+#endif
+
+#if defined(_WIN32) || defined(__WIN32__) || defined(__WINDOWS__)
+ #define ACUTEST_WIN_ 1
+ #include <windows.h>
+ #include <io.h>
+#endif
+
+#ifdef __cplusplus
+ #include <exception>
+#endif
+
+#ifdef __has_include
+ #if __has_include(<valgrind.h>)
+ #include <valgrind.h>
+ #endif
+#endif
+
+/* Enable the use of the non-standard keyword __attribute__ to silence warnings under some compilers */
+#if defined(__GNUC__) || defined(__clang__)
+ #define ACUTEST_ATTRIBUTE_(attr) __attribute__((attr))
+#else
+ #define ACUTEST_ATTRIBUTE_(attr)
+#endif
+
+/* Note our global private identifiers end with '_' to mitigate risk of clash
+ * with the unit tests implementation. */
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+#ifdef _MSC_VER
+ /* In the multi-platform code like ours, we cannot use the non-standard
+ * "safe" functions from Microsoft C lib like e.g. sprintf_s() instead of
+ * standard sprintf(). Hence, lets disable the warning C4996. */
+ #pragma warning(push)
+ #pragma warning(disable: 4996)
+#endif
+
+
+struct acutest_test_ {
+ const char* name;
+ void (*func)(void);
+};
+
+struct acutest_test_data_ {
+ unsigned char flags;
+ double duration;
+};
+
+enum {
+ ACUTEST_FLAG_RUN_ = 1 << 0,
+ ACUTEST_FLAG_SUCCESS_ = 1 << 1,
+ ACUTEST_FLAG_FAILURE_ = 1 << 2,
+};
+
+extern const struct acutest_test_ acutest_list_[];
+
+int acutest_check_(int cond, const char* file, int line, const char* fmt, ...);
+void acutest_case_(const char* fmt, ...);
+void acutest_message_(const char* fmt, ...);
+void acutest_dump_(const char* title, const void* addr, size_t size);
+void acutest_abort_(void) ACUTEST_ATTRIBUTE_(noreturn);
+
+
+#ifndef TEST_NO_MAIN
+
+static char* acutest_argv0_ = NULL;
+static size_t acutest_list_size_ = 0;
+static struct acutest_test_data_* acutest_test_data_ = NULL;
+static size_t acutest_count_ = 0;
+static int acutest_no_exec_ = -1;
+static int acutest_no_summary_ = 0;
+static int acutest_tap_ = 0;
+static int acutest_skip_mode_ = 0;
+static int acutest_worker_ = 0;
+static int acutest_worker_index_ = 0;
+static int acutest_cond_failed_ = 0;
+static int acutest_was_aborted_ = 0;
+static FILE *acutest_xml_output_ = NULL;
+
+static int acutest_stat_failed_units_ = 0;
+static int acutest_stat_run_units_ = 0;
+
+static const struct acutest_test_* acutest_current_test_ = NULL;
+static int acutest_current_index_ = 0;
+static char acutest_case_name_[TEST_CASE_MAXSIZE] = "";
+static int acutest_test_already_logged_ = 0;
+static int acutest_case_already_logged_ = 0;
+static int acutest_verbose_level_ = 2;
+static int acutest_test_failures_ = 0;
+static int acutest_colorize_ = 0;
+static int acutest_timer_ = 0;
+
+static int acutest_abort_has_jmp_buf_ = 0;
+static jmp_buf acutest_abort_jmp_buf_;
+
+
+static void
+acutest_cleanup_(void)
+{
+ free((void*) acutest_test_data_);
+}
+
+static void ACUTEST_ATTRIBUTE_(noreturn)
+acutest_exit_(int exit_code)
+{
+ acutest_cleanup_();
+ exit(exit_code);
+}
+
+#if defined ACUTEST_WIN_
+ typedef LARGE_INTEGER acutest_timer_type_;
+ static LARGE_INTEGER acutest_timer_freq_;
+ static acutest_timer_type_ acutest_timer_start_;
+ static acutest_timer_type_ acutest_timer_end_;
+
+ static void
+ acutest_timer_init_(void)
+ {
+ QueryPerformanceFrequency(&acutest_timer_freq_);
+ }
+
+ static void
+ acutest_timer_get_time_(LARGE_INTEGER* ts)
+ {
+ QueryPerformanceCounter(ts);
+ }
+
+ static double
+ acutest_timer_diff_(LARGE_INTEGER start, LARGE_INTEGER end)
+ {
+ double duration = (double)(end.QuadPart - start.QuadPart);
+ duration /= (double)acutest_timer_freq_.QuadPart;
+ return duration;
+ }
+
+ static void
+ acutest_timer_print_diff_(void)
+ {
+ printf("%.6lf secs", acutest_timer_diff_(acutest_timer_start_, acutest_timer_end_));
+ }
+#elif defined ACUTEST_HAS_POSIX_TIMER_
+ static clockid_t acutest_timer_id_;
+ typedef struct timespec acutest_timer_type_;
+ static acutest_timer_type_ acutest_timer_start_;
+ static acutest_timer_type_ acutest_timer_end_;
+
+ static void
+ acutest_timer_init_(void)
+ {
+ if(acutest_timer_ == 1)
+ acutest_timer_id_ = CLOCK_MONOTONIC;
+ else if(acutest_timer_ == 2)
+ acutest_timer_id_ = CLOCK_PROCESS_CPUTIME_ID;
+ }
+
+ static void
+ acutest_timer_get_time_(struct timespec* ts)
+ {
+ clock_gettime(acutest_timer_id_, ts);
+ }
+
+ static double
+ acutest_timer_diff_(struct timespec start, struct timespec end)
+ {
+ double endns;
+ double startns;
+
+ endns = end.tv_sec;
+ endns *= 1e9;
+ endns += end.tv_nsec;
+
+ startns = start.tv_sec;
+ startns *= 1e9;
+ startns += start.tv_nsec;
+
+ return ((endns - startns)/ 1e9);
+ }
+
+ static void
+ acutest_timer_print_diff_(void)
+ {
+ printf("%.6lf secs",
+ acutest_timer_diff_(acutest_timer_start_, acutest_timer_end_));
+ }
+#else
+ typedef int acutest_timer_type_;
+ static acutest_timer_type_ acutest_timer_start_;
+ static acutest_timer_type_ acutest_timer_end_;
+
+ void
+ acutest_timer_init_(void)
+ {}
+
+ static void
+ acutest_timer_get_time_(int* ts)
+ {
+ (void) ts;
+ }
+
+ static double
+ acutest_timer_diff_(int start, int end)
+ {
+ (void) start;
+ (void) end;
+ return 0.0;
+ }
+
+ static void
+ acutest_timer_print_diff_(void)
+ {}
+#endif
+
+#define ACUTEST_COLOR_DEFAULT_ 0
+#define ACUTEST_COLOR_GREEN_ 1
+#define ACUTEST_COLOR_RED_ 2
+#define ACUTEST_COLOR_DEFAULT_INTENSIVE_ 3
+#define ACUTEST_COLOR_GREEN_INTENSIVE_ 4
+#define ACUTEST_COLOR_RED_INTENSIVE_ 5
+
+static int ACUTEST_ATTRIBUTE_(format (printf, 2, 3))
+acutest_colored_printf_(int color, const char* fmt, ...)
+{
+ va_list args;
+ char buffer[256];
+ int n;
+
+ va_start(args, fmt);
+ vsnprintf(buffer, sizeof(buffer), fmt, args);
+ va_end(args);
+ buffer[sizeof(buffer)-1] = '\0';
+
+ if(!acutest_colorize_) {
+ return printf("%s", buffer);
+ }
+
+#if defined ACUTEST_UNIX_
+ {
+ const char* col_str;
+ switch(color) {
+ case ACUTEST_COLOR_GREEN_: col_str = "\033[0;32m"; break;
+ case ACUTEST_COLOR_RED_: col_str = "\033[0;31m"; break;
+ case ACUTEST_COLOR_GREEN_INTENSIVE_: col_str = "\033[1;32m"; break;
+ case ACUTEST_COLOR_RED_INTENSIVE_: col_str = "\033[1;31m"; break;
+ case ACUTEST_COLOR_DEFAULT_INTENSIVE_: col_str = "\033[1m"; break;
+ default: col_str = "\033[0m"; break;
+ }
+ printf("%s", col_str);
+ n = printf("%s", buffer);
+ printf("\033[0m");
+ return n;
+ }
+#elif defined ACUTEST_WIN_
+ {
+ HANDLE h;
+ CONSOLE_SCREEN_BUFFER_INFO info;
+ WORD attr;
+
+ h = GetStdHandle(STD_OUTPUT_HANDLE);
+ GetConsoleScreenBufferInfo(h, &info);
+
+ switch(color) {
+ case ACUTEST_COLOR_GREEN_: attr = FOREGROUND_GREEN; break;
+ case ACUTEST_COLOR_RED_: attr = FOREGROUND_RED; break;
+ case ACUTEST_COLOR_GREEN_INTENSIVE_: attr = FOREGROUND_GREEN | FOREGROUND_INTENSITY; break;
+ case ACUTEST_COLOR_RED_INTENSIVE_: attr = FOREGROUND_RED | FOREGROUND_INTENSITY; break;
+ case ACUTEST_COLOR_DEFAULT_INTENSIVE_: attr = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY; break;
+ default: attr = 0; break;
+ }
+ if(attr != 0)
+ SetConsoleTextAttribute(h, attr);
+ n = printf("%s", buffer);
+ SetConsoleTextAttribute(h, info.wAttributes);
+ return n;
+ }
+#else
+ n = printf("%s", buffer);
+ return n;
+#endif
+}
+
+static void
+acutest_begin_test_line_(const struct acutest_test_* test)
+{
+ if(!acutest_tap_) {
+ if(acutest_verbose_level_ >= 3) {
+ acutest_colored_printf_(ACUTEST_COLOR_DEFAULT_INTENSIVE_, "Test %s:\n", test->name);
+ acutest_test_already_logged_++;
+ } else if(acutest_verbose_level_ >= 1) {
+ int n;
+ char spaces[48];
+
+ n = acutest_colored_printf_(ACUTEST_COLOR_DEFAULT_INTENSIVE_, "Test %s... ", test->name);
+ memset(spaces, ' ', sizeof(spaces));
+ if(n < (int) sizeof(spaces))
+ printf("%.*s", (int) sizeof(spaces) - n, spaces);
+ } else {
+ acutest_test_already_logged_ = 1;
+ }
+ }
+}
+
+static void
+acutest_finish_test_line_(int result)
+{
+ if(acutest_tap_) {
+ const char* str = (result == 0) ? "ok" : "not ok";
+
+ printf("%s %d - %s\n", str, acutest_current_index_ + 1, acutest_current_test_->name);
+
+ if(result == 0 && acutest_timer_) {
+ printf("# Duration: ");
+ acutest_timer_print_diff_();
+ printf("\n");
+ }
+ } else {
+ int color = (result == 0) ? ACUTEST_COLOR_GREEN_INTENSIVE_ : ACUTEST_COLOR_RED_INTENSIVE_;
+ const char* str = (result == 0) ? "OK" : "FAILED";
+ printf("[ ");
+ acutest_colored_printf_(color, "%s", str);
+ printf(" ]");
+
+ if(result == 0 && acutest_timer_) {
+ printf(" ");
+ acutest_timer_print_diff_();
+ }
+
+ printf("\n");
+ }
+}
+
+static void
+acutest_line_indent_(int level)
+{
+ static const char spaces[] = " ";
+ int n = level * 2;
+
+ if(acutest_tap_ && n > 0) {
+ n--;
+ printf("#");
+ }
+
+ while(n > 16) {
+ printf("%s", spaces);
+ n -= 16;
+ }
+ printf("%.*s", n, spaces);
+}
+
+int ACUTEST_ATTRIBUTE_(format (printf, 4, 5))
+acutest_check_(int cond, const char* file, int line, const char* fmt, ...)
+{
+ const char *result_str;
+ int result_color;
+ int verbose_level;
+
+ if(cond) {
+ result_str = "ok";
+ result_color = ACUTEST_COLOR_GREEN_;
+ verbose_level = 3;
+ } else {
+ if(!acutest_test_already_logged_ && acutest_current_test_ != NULL)
+ acutest_finish_test_line_(-1);
+
+ result_str = "failed";
+ result_color = ACUTEST_COLOR_RED_;
+ verbose_level = 2;
+ acutest_test_failures_++;
+ acutest_test_already_logged_++;
+ }
+
+ if(acutest_verbose_level_ >= verbose_level) {
+ va_list args;
+
+ if(!acutest_case_already_logged_ && acutest_case_name_[0]) {
+ acutest_line_indent_(1);
+ acutest_colored_printf_(ACUTEST_COLOR_DEFAULT_INTENSIVE_, "Case %s:\n", acutest_case_name_);
+ acutest_test_already_logged_++;
+ acutest_case_already_logged_++;
+ }
+
+ acutest_line_indent_(acutest_case_name_[0] ? 2 : 1);
+ if(file != NULL) {
+#ifdef ACUTEST_WIN_
+ const char* lastsep1 = strrchr(file, '\\');
+ const char* lastsep2 = strrchr(file, '/');
+ if(lastsep1 == NULL)
+ lastsep1 = file-1;
+ if(lastsep2 == NULL)
+ lastsep2 = file-1;
+ file = (lastsep1 > lastsep2 ? lastsep1 : lastsep2) + 1;
+#else
+ const char* lastsep = strrchr(file, '/');
+ if(lastsep != NULL)
+ file = lastsep+1;
+#endif
+ printf("%s:%d: Check ", file, line);
+ }
+
+ va_start(args, fmt);
+ vprintf(fmt, args);
+ va_end(args);
+
+ printf("... ");
+ acutest_colored_printf_(result_color, "%s", result_str);
+ printf("\n");
+ acutest_test_already_logged_++;
+ }
+
+ acutest_cond_failed_ = (cond == 0);
+ return !acutest_cond_failed_;
+}
+
+void ACUTEST_ATTRIBUTE_(format (printf, 1, 2))
+acutest_case_(const char* fmt, ...)
+{
+ va_list args;
+
+ if(acutest_verbose_level_ < 2)
+ return;
+
+ if(acutest_case_name_[0]) {
+ acutest_case_already_logged_ = 0;
+ acutest_case_name_[0] = '\0';
+ }
+
+ if(fmt == NULL)
+ return;
+
+ va_start(args, fmt);
+ vsnprintf(acutest_case_name_, sizeof(acutest_case_name_) - 1, fmt, args);
+ va_end(args);
+ acutest_case_name_[sizeof(acutest_case_name_) - 1] = '\0';
+
+ if(acutest_verbose_level_ >= 3) {
+ acutest_line_indent_(1);
+ acutest_colored_printf_(ACUTEST_COLOR_DEFAULT_INTENSIVE_, "Case %s:\n", acutest_case_name_);
+ acutest_test_already_logged_++;
+ acutest_case_already_logged_++;
+ }
+}
+
+void ACUTEST_ATTRIBUTE_(format (printf, 1, 2))
+acutest_message_(const char* fmt, ...)
+{
+ char buffer[TEST_MSG_MAXSIZE];
+ char* line_beg;
+ char* line_end;
+ va_list args;
+
+ if(acutest_verbose_level_ < 2)
+ return;
+
+ /* We allow extra message only when something is already wrong in the
+ * current test. */
+ if(acutest_current_test_ == NULL || !acutest_cond_failed_)
+ return;
+
+ va_start(args, fmt);
+ vsnprintf(buffer, TEST_MSG_MAXSIZE, fmt, args);
+ va_end(args);
+ buffer[TEST_MSG_MAXSIZE-1] = '\0';
+
+ line_beg = buffer;
+ while(1) {
+ line_end = strchr(line_beg, '\n');
+ if(line_end == NULL)
+ break;
+ acutest_line_indent_(acutest_case_name_[0] ? 3 : 2);
+ printf("%.*s\n", (int)(line_end - line_beg), line_beg);
+ line_beg = line_end + 1;
+ }
+ if(line_beg[0] != '\0') {
+ acutest_line_indent_(acutest_case_name_[0] ? 3 : 2);
+ printf("%s\n", line_beg);
+ }
+}
+
+void
+acutest_dump_(const char* title, const void* addr, size_t size)
+{
+ static const size_t BYTES_PER_LINE = 16;
+ size_t line_beg;
+ size_t truncate = 0;
+
+ if(acutest_verbose_level_ < 2)
+ return;
+
+ /* We allow extra message only when something is already wrong in the
+ * current test. */
+ if(acutest_current_test_ == NULL || !acutest_cond_failed_)
+ return;
+
+ if(size > TEST_DUMP_MAXSIZE) {
+ truncate = size - TEST_DUMP_MAXSIZE;
+ size = TEST_DUMP_MAXSIZE;
+ }
+
+ acutest_line_indent_(acutest_case_name_[0] ? 3 : 2);
+ printf((title[strlen(title)-1] == ':') ? "%s\n" : "%s:\n", title);
+
+ for(line_beg = 0; line_beg < size; line_beg += BYTES_PER_LINE) {
+ size_t line_end = line_beg + BYTES_PER_LINE;
+ size_t off;
+
+ acutest_line_indent_(acutest_case_name_[0] ? 4 : 3);
+ printf("%08lx: ", (unsigned long)line_beg);
+ for(off = line_beg; off < line_end; off++) {
+ if(off < size)
+ printf(" %02x", ((const unsigned char*)addr)[off]);
+ else
+ printf(" ");
+ }
+
+ printf(" ");
+ for(off = line_beg; off < line_end; off++) {
+ unsigned char byte = ((const unsigned char*)addr)[off];
+ if(off < size)
+ printf("%c", (iscntrl(byte) ? '.' : byte));
+ else
+ break;
+ }
+
+ printf("\n");
+ }
+
+ if(truncate > 0) {
+ acutest_line_indent_(acutest_case_name_[0] ? 4 : 3);
+ printf(" ... (and more %u bytes)\n", (unsigned) truncate);
+ }
+}
+
+/* This is called just before each test */
+static void
+acutest_init_(const char *test_name)
+{
+#ifdef TEST_INIT
+ TEST_INIT
+ ; /* Allow for a single unterminated function call */
+#endif
+
+ /* Suppress any warnings about unused variable. */
+ (void) test_name;
+}
+
+/* This is called after each test */
+static void
+acutest_fini_(const char *test_name)
+{
+#ifdef TEST_FINI
+ TEST_FINI
+ ; /* Allow for a single unterminated function call */
+#endif
+
+ /* Suppress any warnings about unused variable. */
+ (void) test_name;
+}
+
+void
+acutest_abort_(void)
+{
+ if(acutest_abort_has_jmp_buf_) {
+ longjmp(acutest_abort_jmp_buf_, 1);
+ } else {
+ if(acutest_current_test_ != NULL)
+ acutest_fini_(acutest_current_test_->name);
+ abort();
+ }
+}
+
+static void
+acutest_list_names_(void)
+{
+ const struct acutest_test_* test;
+
+ printf("Unit tests:\n");
+ for(test = &acutest_list_[0]; test->func != NULL; test++)
+ printf(" %s\n", test->name);
+}
+
+static void
+acutest_remember_(int i)
+{
+ if(acutest_test_data_[i].flags & ACUTEST_FLAG_RUN_)
+ return;
+
+ acutest_test_data_[i].flags |= ACUTEST_FLAG_RUN_;
+ acutest_count_++;
+}
+
+static void
+acutest_set_success_(int i, int success)
+{
+ acutest_test_data_[i].flags |= success ? ACUTEST_FLAG_SUCCESS_ : ACUTEST_FLAG_FAILURE_;
+}
+
+static void
+acutest_set_duration_(int i, double duration)
+{
+ acutest_test_data_[i].duration = duration;
+}
+
+static int
+acutest_name_contains_word_(const char* name, const char* pattern)
+{
+ static const char word_delim[] = " \t-_/.,:;";
+ const char* substr;
+ size_t pattern_len;
+
+ pattern_len = strlen(pattern);
+
+ substr = strstr(name, pattern);
+ while(substr != NULL) {
+ int starts_on_word_boundary = (substr == name || strchr(word_delim, substr[-1]) != NULL);
+ int ends_on_word_boundary = (substr[pattern_len] == '\0' || strchr(word_delim, substr[pattern_len]) != NULL);
+
+ if(starts_on_word_boundary && ends_on_word_boundary)
+ return 1;
+
+ substr = strstr(substr+1, pattern);
+ }
+
+ return 0;
+}
+
+static int
+acutest_lookup_(const char* pattern)
+{
+ int i;
+ int n = 0;
+
+ /* Try exact match. */
+ for(i = 0; i < (int) acutest_list_size_; i++) {
+ if(strcmp(acutest_list_[i].name, pattern) == 0) {
+ acutest_remember_(i);
+ n++;
+ break;
+ }
+ }
+ if(n > 0)
+ return n;
+
+ /* Try word match. */
+ for(i = 0; i < (int) acutest_list_size_; i++) {
+ if(acutest_name_contains_word_(acutest_list_[i].name, pattern)) {
+ acutest_remember_(i);
+ n++;
+ }
+ }
+ if(n > 0)
+ return n;
+
+ /* Try relaxed match. */
+ for(i = 0; i < (int) acutest_list_size_; i++) {
+ if(strstr(acutest_list_[i].name, pattern) != NULL) {
+ acutest_remember_(i);
+ n++;
+ }
+ }
+
+ return n;
+}
+
+
+/* Called if anything goes bad in Acutest, or if the unit test ends in other
+ * way then by normal returning from its function (e.g. exception or some
+ * abnormal child process termination). */
+static void ACUTEST_ATTRIBUTE_(format (printf, 1, 2))
+acutest_error_(const char* fmt, ...)
+{
+ if(acutest_verbose_level_ == 0)
+ return;
+
+ if(acutest_verbose_level_ >= 2) {
+ va_list args;
+
+ acutest_line_indent_(1);
+ if(acutest_verbose_level_ >= 3)
+ acutest_colored_printf_(ACUTEST_COLOR_RED_INTENSIVE_, "ERROR: ");
+ va_start(args, fmt);
+ vprintf(fmt, args);
+ va_end(args);
+ printf("\n");
+ }
+
+ if(acutest_verbose_level_ >= 3) {
+ printf("\n");
+ }
+}
+
+/* Call directly the given test unit function. */
+static int
+acutest_do_run_(const struct acutest_test_* test, int index)
+{
+ int status = -1;
+
+ acutest_was_aborted_ = 0;
+ acutest_current_test_ = test;
+ acutest_current_index_ = index;
+ acutest_test_failures_ = 0;
+ acutest_test_already_logged_ = 0;
+ acutest_cond_failed_ = 0;
+
+#ifdef __cplusplus
+ try {
+#endif
+ acutest_init_(test->name);
+ acutest_begin_test_line_(test);
+
+ /* This is good to do in case the test unit crashes. */
+ fflush(stdout);
+ fflush(stderr);
+
+ if(!acutest_worker_) {
+ acutest_abort_has_jmp_buf_ = 1;
+ if(setjmp(acutest_abort_jmp_buf_) != 0) {
+ acutest_was_aborted_ = 1;
+ goto aborted;
+ }
+ }
+
+ acutest_timer_get_time_(&acutest_timer_start_);
+ test->func();
+aborted:
+ acutest_abort_has_jmp_buf_ = 0;
+ acutest_timer_get_time_(&acutest_timer_end_);
+
+ if(acutest_verbose_level_ >= 3) {
+ acutest_line_indent_(1);
+ if(acutest_test_failures_ == 0) {
+ acutest_colored_printf_(ACUTEST_COLOR_GREEN_INTENSIVE_, "SUCCESS: ");
+ printf("All conditions have passed.\n");
+
+ if(acutest_timer_) {
+ acutest_line_indent_(1);
+ printf("Duration: ");
+ acutest_timer_print_diff_();
+ printf("\n");
+ }
+ } else {
+ acutest_colored_printf_(ACUTEST_COLOR_RED_INTENSIVE_, "FAILED: ");
+ if(!acutest_was_aborted_) {
+ printf("%d condition%s %s failed.\n",
+ acutest_test_failures_,
+ (acutest_test_failures_ == 1) ? "" : "s",
+ (acutest_test_failures_ == 1) ? "has" : "have");
+ } else {
+ printf("Aborted.\n");
+ }
+ }
+ printf("\n");
+ } else if(acutest_verbose_level_ >= 1 && acutest_test_failures_ == 0) {
+ acutest_finish_test_line_(0);
+ }
+
+ status = (acutest_test_failures_ == 0) ? 0 : -1;
+
+#ifdef __cplusplus
+ } catch(std::exception& e) {
+ const char* what = e.what();
+ acutest_check_(0, NULL, 0, "Threw std::exception");
+ if(what != NULL)
+ acutest_message_("std::exception::what(): %s", what);
+
+ if(acutest_verbose_level_ >= 3) {
+ acutest_line_indent_(1);
+ acutest_colored_printf_(ACUTEST_COLOR_RED_INTENSIVE_, "FAILED: ");
+ printf("C++ exception.\n\n");
+ }
+ } catch(...) {
+ acutest_check_(0, NULL, 0, "Threw an exception");
+
+ if(acutest_verbose_level_ >= 3) {
+ acutest_line_indent_(1);
+ acutest_colored_printf_(ACUTEST_COLOR_RED_INTENSIVE_, "FAILED: ");
+ printf("C++ exception.\n\n");
+ }
+ }
+#endif
+
+ acutest_fini_(test->name);
+ acutest_case_(NULL);
+ acutest_current_test_ = NULL;
+
+ return status;
+}
+
+/* Trigger the unit test. If possible (and not suppressed) it starts a child
+ * process who calls acutest_do_run_(), otherwise it calls acutest_do_run_()
+ * directly. */
+static void
+acutest_run_(const struct acutest_test_* test, int index, int master_index)
+{
+ int failed = 1;
+ acutest_timer_type_ start, end;
+
+ acutest_current_test_ = test;
+ acutest_test_already_logged_ = 0;
+ acutest_timer_get_time_(&start);
+
+ if(!acutest_no_exec_) {
+
+#if defined(ACUTEST_UNIX_)
+
+ pid_t pid;
+ int exit_code;
+
+ /* Make sure the child starts with empty I/O buffers. */
+ fflush(stdout);
+ fflush(stderr);
+
+ pid = fork();
+ if(pid == (pid_t)-1) {
+ acutest_error_("Cannot fork. %s [%d]", strerror(errno), errno);
+ failed = 1;
+ } else if(pid == 0) {
+ /* Child: Do the test. */
+ acutest_worker_ = 1;
+ failed = (acutest_do_run_(test, index) != 0);
+ acutest_exit_(failed ? 1 : 0);
+ } else {
+ /* Parent: Wait until child terminates and analyze its exit code. */
+ waitpid(pid, &exit_code, 0);
+ if(WIFEXITED(exit_code)) {
+ switch(WEXITSTATUS(exit_code)) {
+ case 0: failed = 0; break; /* test has passed. */
+ case 1: /* noop */ break; /* "normal" failure. */
+ default: acutest_error_("Unexpected exit code [%d]", WEXITSTATUS(exit_code));
+ }
+ } else if(WIFSIGNALED(exit_code)) {
+ char tmp[32];
+ const char* signame;
+ switch(WTERMSIG(exit_code)) {
+ case SIGINT: signame = "SIGINT"; break;
+ case SIGHUP: signame = "SIGHUP"; break;
+ case SIGQUIT: signame = "SIGQUIT"; break;
+ case SIGABRT: signame = "SIGABRT"; break;
+ case SIGKILL: signame = "SIGKILL"; break;
+ case SIGSEGV: signame = "SIGSEGV"; break;
+ case SIGILL: signame = "SIGILL"; break;
+ case SIGTERM: signame = "SIGTERM"; break;
+ default: sprintf(tmp, "signal %d", WTERMSIG(exit_code)); signame = tmp; break;
+ }
+ acutest_error_("Test interrupted by %s.", signame);
+ } else {
+ acutest_error_("Test ended in an unexpected way [%d].", exit_code);
+ }
+ }
+
+#elif defined(ACUTEST_WIN_)
+
+ char buffer[512] = {0};
+ STARTUPINFOA startupInfo;
+ PROCESS_INFORMATION processInfo;
+ DWORD exitCode;
+
+ /* Windows has no fork(). So we propagate all info into the child
+ * through a command line arguments. */
+ _snprintf(buffer, sizeof(buffer)-1,
+ "%s --worker=%d %s --no-exec --no-summary %s --verbose=%d --color=%s -- \"%s\"",
+ acutest_argv0_, index, acutest_timer_ ? "--time" : "",
+ acutest_tap_ ? "--tap" : "", acutest_verbose_level_,
+ acutest_colorize_ ? "always" : "never",
+ test->name);
+ memset(&startupInfo, 0, sizeof(startupInfo));
+ startupInfo.cb = sizeof(STARTUPINFO);
+ if(CreateProcessA(NULL, buffer, NULL, NULL, FALSE, 0, NULL, NULL, &startupInfo, &processInfo)) {
+ WaitForSingleObject(processInfo.hProcess, INFINITE);
+ GetExitCodeProcess(processInfo.hProcess, &exitCode);
+ CloseHandle(processInfo.hThread);
+ CloseHandle(processInfo.hProcess);
+ failed = (exitCode != 0);
+ if(exitCode > 1) {
+ switch(exitCode) {
+ case 3: acutest_error_("Aborted."); break;
+ case 0xC0000005: acutest_error_("Access violation."); break;
+ default: acutest_error_("Test ended in an unexpected way [%lu].", exitCode); break;
+ }
+ }
+ } else {
+ acutest_error_("Cannot create unit test subprocess [%ld].", GetLastError());
+ failed = 1;
+ }
+
+#else
+
+ /* A platform where we don't know how to run child process. */
+ failed = (acutest_do_run_(test, index) != 0);
+
+#endif
+
+ } else {
+ /* Child processes suppressed through --no-exec. */
+ failed = (acutest_do_run_(test, index) != 0);
+ }
+ acutest_timer_get_time_(&end);
+
+ acutest_current_test_ = NULL;
+
+ acutest_stat_run_units_++;
+ if(failed)
+ acutest_stat_failed_units_++;
+
+ acutest_set_success_(master_index, !failed);
+ acutest_set_duration_(master_index, acutest_timer_diff_(start, end));
+}
+
+#if defined(ACUTEST_WIN_)
+/* Callback for SEH events. */
+static LONG CALLBACK
+acutest_seh_exception_filter_(EXCEPTION_POINTERS *ptrs)
+{
+ acutest_check_(0, NULL, 0, "Unhandled SEH exception");
+ acutest_message_("Exception code: 0x%08lx", ptrs->ExceptionRecord->ExceptionCode);
+ acutest_message_("Exception address: 0x%p", ptrs->ExceptionRecord->ExceptionAddress);
+
+ fflush(stdout);
+ fflush(stderr);
+
+ return EXCEPTION_EXECUTE_HANDLER;
+}
+#endif
+
+
+#define ACUTEST_CMDLINE_OPTFLAG_OPTIONALARG_ 0x0001
+#define ACUTEST_CMDLINE_OPTFLAG_REQUIREDARG_ 0x0002
+
+#define ACUTEST_CMDLINE_OPTID_NONE_ 0
+#define ACUTEST_CMDLINE_OPTID_UNKNOWN_ (-0x7fffffff + 0)
+#define ACUTEST_CMDLINE_OPTID_MISSINGARG_ (-0x7fffffff + 1)
+#define ACUTEST_CMDLINE_OPTID_BOGUSARG_ (-0x7fffffff + 2)
+
+typedef struct acutest_test_CMDLINE_OPTION_ {
+ char shortname;
+ const char* longname;
+ int id;
+ unsigned flags;
+} ACUTEST_CMDLINE_OPTION_;
+
+static int
+acutest_cmdline_handle_short_opt_group_(const ACUTEST_CMDLINE_OPTION_* options,
+ const char* arggroup,
+ int (*callback)(int /*optval*/, const char* /*arg*/))
+{
+ const ACUTEST_CMDLINE_OPTION_* opt;
+ int i;
+ int ret = 0;
+
+ for(i = 0; arggroup[i] != '\0'; i++) {
+ for(opt = options; opt->id != 0; opt++) {
+ if(arggroup[i] == opt->shortname)
+ break;
+ }
+
+ if(opt->id != 0 && !(opt->flags & ACUTEST_CMDLINE_OPTFLAG_REQUIREDARG_)) {
+ ret = callback(opt->id, NULL);
+ } else {
+ /* Unknown option. */
+ char badoptname[3];
+ badoptname[0] = '-';
+ badoptname[1] = arggroup[i];
+ badoptname[2] = '\0';
+ ret = callback((opt->id != 0 ? ACUTEST_CMDLINE_OPTID_MISSINGARG_ : ACUTEST_CMDLINE_OPTID_UNKNOWN_),
+ badoptname);
+ }
+
+ if(ret != 0)
+ break;
+ }
+
+ return ret;
+}
+
+#define ACUTEST_CMDLINE_AUXBUF_SIZE_ 32
+
+static int
+acutest_cmdline_read_(const ACUTEST_CMDLINE_OPTION_* options, int argc, char** argv,
+ int (*callback)(int /*optval*/, const char* /*arg*/))
+{
+
+ const ACUTEST_CMDLINE_OPTION_* opt;
+ char auxbuf[ACUTEST_CMDLINE_AUXBUF_SIZE_+1];
+ int after_doubledash = 0;
+ int i = 1;
+ int ret = 0;
+
+ auxbuf[ACUTEST_CMDLINE_AUXBUF_SIZE_] = '\0';
+
+ while(i < argc) {
+ if(after_doubledash || strcmp(argv[i], "-") == 0) {
+ /* Non-option argument. */
+ ret = callback(ACUTEST_CMDLINE_OPTID_NONE_, argv[i]);
+ } else if(strcmp(argv[i], "--") == 0) {
+ /* End of options. All the remaining members are non-option arguments. */
+ after_doubledash = 1;
+ } else if(argv[i][0] != '-') {
+ /* Non-option argument. */
+ ret = callback(ACUTEST_CMDLINE_OPTID_NONE_, argv[i]);
+ } else {
+ for(opt = options; opt->id != 0; opt++) {
+ if(opt->longname != NULL && strncmp(argv[i], "--", 2) == 0) {
+ size_t len = strlen(opt->longname);
+ if(strncmp(argv[i]+2, opt->longname, len) == 0) {
+ /* Regular long option. */
+ if(argv[i][2+len] == '\0') {
+ /* with no argument provided. */
+ if(!(opt->flags & ACUTEST_CMDLINE_OPTFLAG_REQUIREDARG_))
+ ret = callback(opt->id, NULL);
+ else
+ ret = callback(ACUTEST_CMDLINE_OPTID_MISSINGARG_, argv[i]);
+ break;
+ } else if(argv[i][2+len] == '=') {
+ /* with an argument provided. */
+ if(opt->flags & (ACUTEST_CMDLINE_OPTFLAG_OPTIONALARG_ | ACUTEST_CMDLINE_OPTFLAG_REQUIREDARG_)) {
+ ret = callback(opt->id, argv[i]+2+len+1);
+ } else {
+ sprintf(auxbuf, "--%s", opt->longname);
+ ret = callback(ACUTEST_CMDLINE_OPTID_BOGUSARG_, auxbuf);
+ }
+ break;
+ } else {
+ continue;
+ }
+ }
+ } else if(opt->shortname != '\0' && argv[i][0] == '-') {
+ if(argv[i][1] == opt->shortname) {
+ /* Regular short option. */
+ if(opt->flags & ACUTEST_CMDLINE_OPTFLAG_REQUIREDARG_) {
+ if(argv[i][2] != '\0')
+ ret = callback(opt->id, argv[i]+2);
+ else if(i+1 < argc)
+ ret = callback(opt->id, argv[++i]);
+ else
+ ret = callback(ACUTEST_CMDLINE_OPTID_MISSINGARG_, argv[i]);
+ break;
+ } else {
+ ret = callback(opt->id, NULL);
+
+ /* There might be more (argument-less) short options
+ * grouped together. */
+ if(ret == 0 && argv[i][2] != '\0')
+ ret = acutest_cmdline_handle_short_opt_group_(options, argv[i]+2, callback);
+ break;
+ }
+ }
+ }
+ }
+
+ if(opt->id == 0) { /* still not handled? */
+ if(argv[i][0] != '-') {
+ /* Non-option argument. */
+ ret = callback(ACUTEST_CMDLINE_OPTID_NONE_, argv[i]);
+ } else {
+ /* Unknown option. */
+ char* badoptname = argv[i];
+
+ if(strncmp(badoptname, "--", 2) == 0) {
+ /* Strip any argument from the long option. */
+ char* assignment = strchr(badoptname, '=');
+ if(assignment != NULL) {
+ size_t len = assignment - badoptname;
+ if(len > ACUTEST_CMDLINE_AUXBUF_SIZE_)
+ len = ACUTEST_CMDLINE_AUXBUF_SIZE_;
+ strncpy(auxbuf, badoptname, len);
+ auxbuf[len] = '\0';
+ badoptname = auxbuf;
+ }
+ }
+
+ ret = callback(ACUTEST_CMDLINE_OPTID_UNKNOWN_, badoptname);
+ }
+ }
+ }
+
+ if(ret != 0)
+ return ret;
+ i++;
+ }
+
+ return ret;
+}
+
+static void
+acutest_help_(void)
+{
+ printf("Usage: %s [options] [test...]\n", acutest_argv0_);
+ printf("\n");
+ printf("Run the specified unit tests; or if the option '--skip' is used, run all\n");
+ printf("tests in the suite but those listed. By default, if no tests are specified\n");
+ printf("on the command line, all unit tests in the suite are run.\n");
+ printf("\n");
+ printf("Options:\n");
+ printf(" -s, --skip Execute all unit tests but the listed ones\n");
+ printf(" --exec[=WHEN] If supported, execute unit tests as child processes\n");
+ printf(" (WHEN is one of 'auto', 'always', 'never')\n");
+ printf(" -E, --no-exec Same as --exec=never\n");
+#if defined ACUTEST_WIN_
+ printf(" -t, --time Measure test duration\n");
+#elif defined ACUTEST_HAS_POSIX_TIMER_
+ printf(" -t, --time Measure test duration (real time)\n");
+ printf(" --time=TIMER Measure test duration, using given timer\n");
+ printf(" (TIMER is one of 'real', 'cpu')\n");
+#endif
+ printf(" --no-summary Suppress printing of test results summary\n");
+ printf(" --tap Produce TAP-compliant output\n");
+ printf(" (See https://testanything.org/)\n");
+ printf(" -x, --xml-output=FILE Enable XUnit output to the given file\n");
+ printf(" -l, --list List unit tests in the suite and exit\n");
+ printf(" -v, --verbose Make output more verbose\n");
+ printf(" --verbose=LEVEL Set verbose level to LEVEL:\n");
+ printf(" 0 ... Be silent\n");
+ printf(" 1 ... Output one line per test (and summary)\n");
+ printf(" 2 ... As 1 and failed conditions (this is default)\n");
+ printf(" 3 ... As 1 and all conditions (and extended summary)\n");
+ printf(" -q, --quiet Same as --verbose=0\n");
+ printf(" --color[=WHEN] Enable colorized output\n");
+ printf(" (WHEN is one of 'auto', 'always', 'never')\n");
+ printf(" --no-color Same as --color=never\n");
+ printf(" -h, --help Display this help and exit\n");
+
+ if(acutest_list_size_ < 16) {
+ printf("\n");
+ acutest_list_names_();
+ }
+}
+
+static const ACUTEST_CMDLINE_OPTION_ acutest_cmdline_options_[] = {
+ { 's', "skip", 's', 0 },
+ { 0, "exec", 'e', ACUTEST_CMDLINE_OPTFLAG_OPTIONALARG_ },
+ { 'E', "no-exec", 'E', 0 },
+#if defined ACUTEST_WIN_
+ { 't', "time", 't', 0 },
+ { 0, "timer", 't', 0 }, /* kept for compatibility */
+#elif defined ACUTEST_HAS_POSIX_TIMER_
+ { 't', "time", 't', ACUTEST_CMDLINE_OPTFLAG_OPTIONALARG_ },
+ { 0, "timer", 't', ACUTEST_CMDLINE_OPTFLAG_OPTIONALARG_ }, /* kept for compatibility */
+#endif
+ { 0, "no-summary", 'S', 0 },
+ { 0, "tap", 'T', 0 },
+ { 'l', "list", 'l', 0 },
+ { 'v', "verbose", 'v', ACUTEST_CMDLINE_OPTFLAG_OPTIONALARG_ },
+ { 'q', "quiet", 'q', 0 },
+ { 0, "color", 'c', ACUTEST_CMDLINE_OPTFLAG_OPTIONALARG_ },
+ { 0, "no-color", 'C', 0 },
+ { 'h', "help", 'h', 0 },
+ { 0, "worker", 'w', ACUTEST_CMDLINE_OPTFLAG_REQUIREDARG_ }, /* internal */
+ { 'x', "xml-output", 'x', ACUTEST_CMDLINE_OPTFLAG_REQUIREDARG_ },
+ { 0, NULL, 0, 0 }
+};
+
+static int
+acutest_cmdline_callback_(int id, const char* arg)
+{
+ switch(id) {
+ case 's':
+ acutest_skip_mode_ = 1;
+ break;
+
+ case 'e':
+ if(arg == NULL || strcmp(arg, "always") == 0) {
+ acutest_no_exec_ = 0;
+ } else if(strcmp(arg, "never") == 0) {
+ acutest_no_exec_ = 1;
+ } else if(strcmp(arg, "auto") == 0) {
+ /*noop*/
+ } else {
+ fprintf(stderr, "%s: Unrecognized argument '%s' for option --exec.\n", acutest_argv0_, arg);
+ fprintf(stderr, "Try '%s --help' for more information.\n", acutest_argv0_);
+ acutest_exit_(2);
+ }
+ break;
+
+ case 'E':
+ acutest_no_exec_ = 1;
+ break;
+
+ case 't':
+#if defined ACUTEST_WIN_ || defined ACUTEST_HAS_POSIX_TIMER_
+ if(arg == NULL || strcmp(arg, "real") == 0) {
+ acutest_timer_ = 1;
+ #ifndef ACUTEST_WIN_
+ } else if(strcmp(arg, "cpu") == 0) {
+ acutest_timer_ = 2;
+ #endif
+ } else {
+ fprintf(stderr, "%s: Unrecognized argument '%s' for option --time.\n", acutest_argv0_, arg);
+ fprintf(stderr, "Try '%s --help' for more information.\n", acutest_argv0_);
+ acutest_exit_(2);
+ }
+#endif
+ break;
+
+ case 'S':
+ acutest_no_summary_ = 1;
+ break;
+
+ case 'T':
+ acutest_tap_ = 1;
+ break;
+
+ case 'l':
+ acutest_list_names_();
+ acutest_exit_(0);
+ break;
+
+ case 'v':
+ acutest_verbose_level_ = (arg != NULL ? atoi(arg) : acutest_verbose_level_+1);
+ break;
+
+ case 'q':
+ acutest_verbose_level_ = 0;
+ break;
+
+ case 'c':
+ if(arg == NULL || strcmp(arg, "always") == 0) {
+ acutest_colorize_ = 1;
+ } else if(strcmp(arg, "never") == 0) {
+ acutest_colorize_ = 0;
+ } else if(strcmp(arg, "auto") == 0) {
+ /*noop*/
+ } else {
+ fprintf(stderr, "%s: Unrecognized argument '%s' for option --color.\n", acutest_argv0_, arg);
+ fprintf(stderr, "Try '%s --help' for more information.\n", acutest_argv0_);
+ acutest_exit_(2);
+ }
+ break;
+
+ case 'C':
+ acutest_colorize_ = 0;
+ break;
+
+ case 'h':
+ acutest_help_();
+ acutest_exit_(0);
+ break;
+
+ case 'w':
+ acutest_worker_ = 1;
+ acutest_worker_index_ = atoi(arg);
+ break;
+ case 'x':
+ acutest_xml_output_ = fopen(arg, "w");
+ if (!acutest_xml_output_) {
+ fprintf(stderr, "Unable to open '%s': %s\n", arg, strerror(errno));
+ acutest_exit_(2);
+ }
+ break;
+
+ case 0:
+ if(acutest_lookup_(arg) == 0) {
+ fprintf(stderr, "%s: Unrecognized unit test '%s'\n", acutest_argv0_, arg);
+ fprintf(stderr, "Try '%s --list' for list of unit tests.\n", acutest_argv0_);
+ acutest_exit_(2);
+ }
+ break;
+
+ case ACUTEST_CMDLINE_OPTID_UNKNOWN_:
+ fprintf(stderr, "Unrecognized command line option '%s'.\n", arg);
+ fprintf(stderr, "Try '%s --help' for more information.\n", acutest_argv0_);
+ acutest_exit_(2);
+ break;
+
+ case ACUTEST_CMDLINE_OPTID_MISSINGARG_:
+ fprintf(stderr, "The command line option '%s' requires an argument.\n", arg);
+ fprintf(stderr, "Try '%s --help' for more information.\n", acutest_argv0_);
+ acutest_exit_(2);
+ break;
+
+ case ACUTEST_CMDLINE_OPTID_BOGUSARG_:
+ fprintf(stderr, "The command line option '%s' does not expect an argument.\n", arg);
+ fprintf(stderr, "Try '%s --help' for more information.\n", acutest_argv0_);
+ acutest_exit_(2);
+ break;
+ }
+
+ return 0;
+}
+
+
+#ifdef ACUTEST_LINUX_
+static int
+acutest_is_tracer_present_(void)
+{
+ /* Must be large enough so the line 'TracerPid: ${PID}' can fit in. */
+ static const int OVERLAP = 32;
+
+ char buf[256+OVERLAP+1];
+ int tracer_present = 0;
+ int fd;
+ size_t n_read = 0;
+
+ fd = open("/proc/self/status", O_RDONLY);
+ if(fd == -1)
+ return 0;
+
+ while(1) {
+ static const char pattern[] = "TracerPid:";
+ const char* field;
+
+ while(n_read < sizeof(buf) - 1) {
+ ssize_t n;
+
+ n = read(fd, buf + n_read, sizeof(buf) - 1 - n_read);
+ if(n <= 0)
+ break;
+ n_read += n;
+ }
+ buf[n_read] = '\0';
+
+ field = strstr(buf, pattern);
+ if(field != NULL && field < buf + sizeof(buf) - OVERLAP) {
+ pid_t tracer_pid = (pid_t) atoi(field + sizeof(pattern) - 1);
+ tracer_present = (tracer_pid != 0);
+ break;
+ }
+
+ if(n_read == sizeof(buf)-1) {
+ memmove(buf, buf + sizeof(buf)-1 - OVERLAP, OVERLAP);
+ n_read = OVERLAP;
+ } else {
+ break;
+ }
+ }
+
+ close(fd);
+ return tracer_present;
+}
+#endif
+
+int
+main(int argc, char** argv)
+{
+ int i;
+
+ acutest_argv0_ = argv[0];
+
+#if defined ACUTEST_UNIX_
+ acutest_colorize_ = isatty(STDOUT_FILENO);
+#elif defined ACUTEST_WIN_
+ #if defined _BORLANDC_
+ acutest_colorize_ = isatty(_fileno(stdout));
+ #else
+ acutest_colorize_ = _isatty(_fileno(stdout));
+ #endif
+#else
+ acutest_colorize_ = 0;
+#endif
+
+ /* Count all test units */
+ acutest_list_size_ = 0;
+ for(i = 0; acutest_list_[i].func != NULL; i++)
+ acutest_list_size_++;
+
+ acutest_test_data_ = (struct acutest_test_data_*)calloc(acutest_list_size_, sizeof(struct acutest_test_data_));
+ if(acutest_test_data_ == NULL) {
+ fprintf(stderr, "Out of memory.\n");
+ acutest_exit_(2);
+ }
+
+ /* Parse options */
+ acutest_cmdline_read_(acutest_cmdline_options_, argc, argv, acutest_cmdline_callback_);
+
+ /* Initialize the proper timer. */
+ acutest_timer_init_();
+
+#if defined(ACUTEST_WIN_)
+ SetUnhandledExceptionFilter(acutest_seh_exception_filter_);
+#ifdef _MSC_VER
+ _set_abort_behavior(0, _WRITE_ABORT_MSG);
+#endif
+#endif
+
+ /* By default, we want to run all tests. */
+ if(acutest_count_ == 0) {
+ for(i = 0; acutest_list_[i].func != NULL; i++)
+ acutest_remember_(i);
+ }
+
+ /* Guess whether we want to run unit tests as child processes. */
+ if(acutest_no_exec_ < 0) {
+ acutest_no_exec_ = 0;
+
+ if(acutest_count_ <= 1) {
+ acutest_no_exec_ = 1;
+ } else {
+#ifdef ACUTEST_WIN_
+ if(IsDebuggerPresent())
+ acutest_no_exec_ = 1;
+#endif
+#ifdef ACUTEST_LINUX_
+ if(acutest_is_tracer_present_())
+ acutest_no_exec_ = 1;
+#endif
+#ifdef RUNNING_ON_VALGRIND
+ /* RUNNING_ON_VALGRIND is provided by optionally included <valgrind.h> */
+ if(RUNNING_ON_VALGRIND)
+ acutest_no_exec_ = 1;
+#endif
+ }
+ }
+
+ if(acutest_tap_) {
+ /* TAP requires we know test result ("ok", "not ok") before we output
+ * anything about the test, and this gets problematic for larger verbose
+ * levels. */
+ if(acutest_verbose_level_ > 2)
+ acutest_verbose_level_ = 2;
+
+ /* TAP harness should provide some summary. */
+ acutest_no_summary_ = 1;
+
+ if(!acutest_worker_)
+ printf("1..%d\n", (int) acutest_count_);
+ }
+
+ int index = acutest_worker_index_;
+ for(i = 0; acutest_list_[i].func != NULL; i++) {
+ int run = (acutest_test_data_[i].flags & ACUTEST_FLAG_RUN_);
+ if (acutest_skip_mode_) /* Run all tests except those listed. */
+ run = !run;
+ if(run)
+ acutest_run_(&acutest_list_[i], index++, i);
+ }
+
+ /* Write a summary */
+ if(!acutest_no_summary_ && acutest_verbose_level_ >= 1) {
+ if(acutest_verbose_level_ >= 3) {
+ acutest_colored_printf_(ACUTEST_COLOR_DEFAULT_INTENSIVE_, "Summary:\n");
+
+ printf(" Count of all unit tests: %4d\n", (int) acutest_list_size_);
+ printf(" Count of run unit tests: %4d\n", acutest_stat_run_units_);
+ printf(" Count of failed unit tests: %4d\n", acutest_stat_failed_units_);
+ printf(" Count of skipped unit tests: %4d\n", (int) acutest_list_size_ - acutest_stat_run_units_);
+ }
+
+ if(acutest_stat_failed_units_ == 0) {
+ acutest_colored_printf_(ACUTEST_COLOR_GREEN_INTENSIVE_, "SUCCESS:");
+ printf(" All unit tests have passed.\n");
+ } else {
+ acutest_colored_printf_(ACUTEST_COLOR_RED_INTENSIVE_, "FAILED:");
+ printf(" %d of %d unit tests %s failed.\n",
+ acutest_stat_failed_units_, acutest_stat_run_units_,
+ (acutest_stat_failed_units_ == 1) ? "has" : "have");
+ }
+
+ if(acutest_verbose_level_ >= 3)
+ printf("\n");
+ }
+
+ if (acutest_xml_output_) {
+#if defined ACUTEST_UNIX_
+ char *suite_name = basename(argv[0]);
+#elif defined ACUTEST_WIN_
+ char suite_name[_MAX_FNAME];
+ _splitpath(argv[0], NULL, NULL, suite_name, NULL);
+#else
+ const char *suite_name = argv[0];
+#endif
+ fprintf(acutest_xml_output_, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+ fprintf(acutest_xml_output_, "<testsuite name=\"%s\" tests=\"%d\" errors=\"%d\" failures=\"%d\" skip=\"%d\">\n",
+ suite_name, (int)acutest_list_size_, acutest_stat_failed_units_, acutest_stat_failed_units_,
+ (int)acutest_list_size_ - acutest_stat_run_units_);
+ for(i = 0; acutest_list_[i].func != NULL; i++) {
+ struct acutest_test_data_ *details = &acutest_test_data_[i];
+ fprintf(acutest_xml_output_, " <testcase name=\"%s\" time=\"%.2f\">\n", acutest_list_[i].name, details->duration);
+ if (details->flags & ACUTEST_FLAG_FAILURE_)
+ fprintf(acutest_xml_output_, " <failure />\n");
+ if (!(details->flags & ACUTEST_FLAG_FAILURE_) && !(details->flags & ACUTEST_FLAG_SUCCESS_))
+ fprintf(acutest_xml_output_, " <skipped />\n");
+ fprintf(acutest_xml_output_, " </testcase>\n");
+ }
+ fprintf(acutest_xml_output_, "</testsuite>\n");
+ fclose(acutest_xml_output_);
+ }
+
+ acutest_cleanup_();
+
+ return (acutest_stat_failed_units_ == 0) ? 0 : 1;
+}
+
+
+#endif /* #ifndef TEST_NO_MAIN */
+
+#ifdef _MSC_VER
+ #pragma warning(pop)
+#endif
+
+#ifdef __cplusplus
+ } /* extern "C" */
+#endif
+
+#endif /* #ifndef ACUTEST_H */
diff --git a/src/fluent-bit/lib/cmetrics/tests/null_label.c b/src/fluent-bit/lib/cmetrics/tests/null_label.c
new file mode 100644
index 000000000..2d23bc131
--- /dev/null
+++ b/src/fluent-bit/lib/cmetrics/tests/null_label.c
@@ -0,0 +1,167 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* CMetrics
+ * ========
+ * Copyright 2021-2022 The CMetrics Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cmetrics/cmetrics.h>
+#include <cmetrics/cmt_counter.h>
+#include <cmetrics/cmt_encode_prometheus.h>
+
+#include "cmt_tests.h"
+
+void test_labels()
+{
+ int ret;
+ double val = 1;
+ uint64_t ts;
+ struct cmt *cmt;
+ struct cmt_counter *c;
+
+ cmt = cmt_create();
+ c = cmt_counter_create(cmt, "test", "dummy", "labels", "testing labels",
+ 6, (char *[]) {"A", "B", "C", "D", "E", "F"});
+
+ ts = cfl_time_now();
+
+ ret = cmt_counter_get_val(c, 0, NULL, &val);
+ TEST_CHECK(ret == -1);
+ TEST_CHECK((uint64_t) val == 1);
+
+ cmt_counter_inc(c, ts, 0, NULL);
+ cmt_counter_add(c, ts, 2, 0, NULL);
+ cmt_counter_get_val(c, 0, NULL, &val);
+ TEST_CHECK((uint64_t) val == 3);
+
+ /* --- case 1 --- */
+ cmt_counter_inc(c, ts, 6, (char *[]) {"1", NULL, "98", NULL, NULL, NULL});
+
+ /* check retrieval with no labels */
+ cmt_counter_get_val(c, 0, NULL, &val);
+ TEST_CHECK((uint64_t) val == 3);
+
+ /* check real value */
+ cmt_counter_get_val(c, 6, (char *[]) {"1", NULL, "98", NULL, NULL, NULL}, &val);
+ TEST_CHECK((uint64_t) val == 1);
+
+
+ /* --- case 2 --- */
+ cmt_counter_set(c, ts, 5, 6, (char *[]) {"1", "2", "98", "100", "200", "300"});
+
+ /* check retrieval with no labels */
+ cmt_counter_get_val(c, 0, NULL, &val);
+ TEST_CHECK((uint64_t) val == 3);
+
+ /* check real value */
+ cmt_counter_get_val(c, 6, (char *[]) {"1", "2", "98", "100", "200", "300"}, &val);
+ TEST_CHECK((uint64_t) val == 5);
+
+ /* --- check that 'case 1' still matches --- */
+ cmt_counter_get_val(c, 0, NULL, &val);
+ TEST_CHECK((uint64_t) val == 3);
+
+ /* check real value */
+ cmt_counter_get_val(c, 6, (char *[]) {"1", NULL, "98", NULL, NULL, NULL}, &val);
+ TEST_CHECK((uint64_t) val == 1);
+
+ cmt_destroy(cmt);
+}
+
+void test_encoding()
+{
+ cfl_sds_t result;
+ struct cmt *cmt;
+ struct cmt_counter *c;
+
+ cmt = cmt_create();
+ c = cmt_counter_create(cmt, "test", "dummy", "labels", "testing labels",
+ 6, (char *[]) {"A", "B", "C", "D", "E", "F"});
+
+ cmt_counter_inc(c, 0, 6, (char *[]) {NULL,NULL,NULL,NULL,NULL,NULL});
+ cmt_counter_inc(c, 0, 6, (char *[]) {NULL,NULL,NULL,NULL,NULL,NULL});
+ result = cmt_encode_prometheus_create(cmt, CMT_TRUE);
+ TEST_CHECK(strcmp(result,
+ "# HELP test_dummy_labels testing labels\n"
+ "# TYPE test_dummy_labels counter\n"
+ "test_dummy_labels 2 0\n"
+ ) == 0);
+ cfl_sds_destroy(result);
+
+ cmt_counter_inc(c, 0, 6, (char *[]) {NULL,"b",NULL,NULL,NULL,NULL});
+ result = cmt_encode_prometheus_create(cmt, CMT_TRUE);
+ TEST_CHECK(strcmp(result,
+ "# HELP test_dummy_labels testing labels\n"
+ "# TYPE test_dummy_labels counter\n"
+ "test_dummy_labels 2 0\n"
+ "test_dummy_labels{B=\"b\"} 1 0\n"
+ ) == 0);
+ cfl_sds_destroy(result);
+
+ cmt_counter_inc(c, 0, 6, (char *[]) {NULL,"b",NULL,NULL,NULL,NULL});
+ result = cmt_encode_prometheus_create(cmt, CMT_TRUE);
+ TEST_CHECK(strcmp(result,
+ "# HELP test_dummy_labels testing labels\n"
+ "# TYPE test_dummy_labels counter\n"
+ "test_dummy_labels 2 0\n"
+ "test_dummy_labels{B=\"b\"} 2 0\n"
+ ) == 0);
+ cfl_sds_destroy(result);
+
+
+ cmt_counter_set(c, 0, 5, 6, (char *[]) {NULL,NULL,NULL,"d",NULL,NULL});
+ result = cmt_encode_prometheus_create(cmt, CMT_TRUE);
+ TEST_CHECK(strcmp(result,
+ "# HELP test_dummy_labels testing labels\n"
+ "# TYPE test_dummy_labels counter\n"
+ "test_dummy_labels 2 0\n"
+ "test_dummy_labels{B=\"b\"} 2 0\n"
+ "test_dummy_labels{D=\"d\"} 5 0\n"
+ ) == 0);
+ cfl_sds_destroy(result);
+
+ cmt_counter_set(c, 0, 50, 6, (char *[]) {NULL,"b",NULL,"d",NULL,"f"});
+ result = cmt_encode_prometheus_create(cmt, CMT_TRUE);
+ TEST_CHECK(strcmp(result,
+ "# HELP test_dummy_labels testing labels\n"
+ "# TYPE test_dummy_labels counter\n"
+ "test_dummy_labels 2 0\n"
+ "test_dummy_labels{B=\"b\"} 2 0\n"
+ "test_dummy_labels{D=\"d\"} 5 0\n"
+ "test_dummy_labels{B=\"b\",D=\"d\",F=\"f\"} 50 0\n"
+ ) == 0);
+ cfl_sds_destroy(result);
+
+ cmt_counter_inc(c, 0, 6, (char *[]) {"a","b","c","d","e","f"});
+ result = cmt_encode_prometheus_create(cmt, CMT_TRUE);
+ TEST_CHECK(strcmp(result,
+ "# HELP test_dummy_labels testing labels\n"
+ "# TYPE test_dummy_labels counter\n"
+ "test_dummy_labels 2 0\n"
+ "test_dummy_labels{B=\"b\"} 2 0\n"
+ "test_dummy_labels{D=\"d\"} 5 0\n"
+ "test_dummy_labels{B=\"b\",D=\"d\",F=\"f\"} 50 0\n"
+ "test_dummy_labels{A=\"a\",B=\"b\",C=\"c\",D=\"d\",E=\"e\",F=\"f\"} 1 0\n"
+ ) == 0);
+ cfl_sds_destroy(result);
+
+ cmt_destroy(cmt);
+}
+
+TEST_LIST = {
+ {"labels", test_labels},
+ {"encoding", test_encoding},
+ { 0 }
+};
diff --git a/src/fluent-bit/lib/cmetrics/tests/prometheus_lexer.c b/src/fluent-bit/lib/cmetrics/tests/prometheus_lexer.c
new file mode 100644
index 000000000..fe132f817
--- /dev/null
+++ b/src/fluent-bit/lib/cmetrics/tests/prometheus_lexer.c
@@ -0,0 +1,218 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* CMetrics
+ * ========
+ * Copyright 2021-2022 The CMetrics Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cmetrics/cmetrics.h>
+#include <cmetrics/cmt_counter.h>
+#include <cmetrics/cmt_decode_prometheus.h>
+#include <cmetrics/cmt_encode_prometheus_remote_write.h>
+#include <stdio.h>
+
+#include "cmt_decode_prometheus_parser.h"
+#include "cmt_tests.h"
+
+struct fixture {
+ yyscan_t scanner;
+ YY_BUFFER_STATE buf;
+ YYSTYPE lval;
+ struct cmt_decode_prometheus_context context;
+ const char *text;
+};
+
+struct fixture *init(const char *test)
+{
+ struct fixture *f = malloc(sizeof(*f));
+ memset(f, 0, sizeof(*f));
+ cmt_decode_prometheus_lex_init(&f->scanner);
+ f->buf = cmt_decode_prometheus__scan_string(test, f->scanner);
+ return f;
+}
+
+void destroy(struct fixture *f)
+{
+ cmt_decode_prometheus__delete_buffer(f->buf, f->scanner);
+ cmt_decode_prometheus_lex_destroy(f->scanner);
+ free(f);
+}
+
+int lex(struct fixture *f)
+{
+ return cmt_decode_prometheus_lex(&f->lval, f->scanner, &(f->context));
+}
+
+void test_comment()
+{
+ struct fixture *f = init("# this is just a comment");
+ TEST_CHECK(lex(f) == 0); // 0 means EOF
+ destroy(f);
+}
+
+void test_help()
+{
+ struct fixture *f = init("# HELP cmt_labels_test Static \\\\labels\\n test");
+
+ TEST_CHECK(lex(f) == HELP);
+ TEST_CHECK(strcmp(f->lval.str, "cmt_labels_test") == 0);
+ cfl_sds_destroy(f->lval.str);
+
+ TEST_CHECK(lex(f) == METRIC_DOC);
+ TEST_CHECK(strcmp(f->lval.str, "Static \\labels\n test") == 0);
+ cfl_sds_destroy(f->lval.str);
+
+ destroy(f);
+
+ f = init("# HELP cmt_labels_test Static \\\\labels\\n test\n");
+
+ TEST_CHECK(lex(f) == HELP);
+ TEST_CHECK(strcmp(f->lval.str, "cmt_labels_test") == 0);
+ cfl_sds_destroy(f->lval.str);
+
+ TEST_CHECK(lex(f) == METRIC_DOC);
+ TEST_CHECK(strcmp(f->lval.str, "Static \\labels\n test") == 0);
+ cfl_sds_destroy(f->lval.str);
+
+ destroy(f);
+}
+
+void test_type()
+{
+ struct fixture *f = init("# TYPE metric_name gauge");
+
+ TEST_CHECK(lex(f) == TYPE);
+ TEST_CHECK(strcmp(f->lval.str, "metric_name") == 0);
+ cfl_sds_destroy(f->lval.str);
+
+ TEST_CHECK(lex(f) == GAUGE);
+
+ destroy(f);
+}
+
+void test_simple()
+{
+ struct fixture *f = init(
+ "# HELP cmt_labels_test Static labels test\n"
+ "# TYPE cmt_labels_test counter\n"
+ "cmt_labels_test 1 0\n"
+ "metric2{host=\"calyptia.com\",app=\"cmetrics \\n \\\\ \\\"\"} 2.5 0\n"
+ "# HELP metric1 Second HELP tag\n"
+ "metric1{escapes=\"\\n \\\\ \\\"\"} 4.12 5\n"
+ );
+
+ TEST_CHECK(lex(f) == HELP);
+ TEST_CHECK(strcmp(f->lval.str, "cmt_labels_test") == 0);
+ cfl_sds_destroy(f->lval.str);
+
+ TEST_CHECK(lex(f) == METRIC_DOC);
+ TEST_CHECK(strcmp(f->lval.str, "Static labels test") == 0);
+ cfl_sds_destroy(f->lval.str);
+
+ TEST_CHECK(lex(f) == TYPE);
+ TEST_CHECK(strcmp(f->lval.str, "cmt_labels_test") == 0);
+ cfl_sds_destroy(f->lval.str);
+
+ TEST_CHECK(lex(f) == COUNTER);
+
+ TEST_CHECK(lex(f) == IDENTIFIER);
+ TEST_CHECK(strcmp(f->lval.str, "cmt_labels_test") == 0);
+ cfl_sds_destroy(f->lval.str);
+
+ TEST_CHECK(lex(f) == NUMSTR);
+ TEST_CHECK(strcmp(f->lval.numstr, "1") == 0);
+
+ TEST_CHECK(lex(f) == NUMSTR);
+ TEST_CHECK(strcmp(f->lval.numstr, "0") == 0);
+
+ TEST_CHECK(lex(f) == IDENTIFIER);
+ TEST_CHECK(strcmp(f->lval.str, "metric2") == 0);
+ cfl_sds_destroy(f->lval.str);
+
+ TEST_CHECK(lex(f) == '{');
+
+ TEST_CHECK(lex(f) == IDENTIFIER);
+ TEST_CHECK(strcmp(f->lval.str, "host") == 0);
+ cfl_sds_destroy(f->lval.str);
+
+ TEST_CHECK(lex(f) == '=');
+
+ TEST_CHECK(lex(f) == QUOTED);
+ TEST_CHECK(strcmp(f->lval.str, "calyptia.com") == 0);
+ cfl_sds_destroy(f->lval.str);
+
+ TEST_CHECK(lex(f) == ',');
+
+ TEST_CHECK(lex(f) == IDENTIFIER);
+ TEST_CHECK(strcmp(f->lval.str, "app") == 0);
+ cfl_sds_destroy(f->lval.str);
+
+ TEST_CHECK(lex(f) == '=');
+
+ TEST_CHECK(lex(f) == QUOTED);
+ TEST_CHECK(strcmp(f->lval.str, "cmetrics \n \\ \"") == 0);
+ cfl_sds_destroy(f->lval.str);
+
+ TEST_CHECK(lex(f) == '}');
+
+ TEST_CHECK(lex(f) == NUMSTR);
+ TEST_CHECK(strcmp(f->lval.numstr, "2.5") == 0);
+
+ TEST_CHECK(lex(f) == NUMSTR);
+ TEST_CHECK(strcmp(f->lval.numstr, "0") == 0);
+
+ TEST_CHECK(lex(f) == HELP);
+ TEST_CHECK(strcmp(f->lval.str, "metric1") == 0);
+ cfl_sds_destroy(f->lval.str);
+
+ TEST_CHECK(lex(f) == METRIC_DOC);
+ TEST_CHECK(strcmp(f->lval.str, "Second HELP tag") == 0);
+ cfl_sds_destroy(f->lval.str);
+
+ TEST_CHECK(lex(f) == IDENTIFIER);
+ TEST_CHECK(strcmp(f->lval.str, "metric1") == 0);
+ cfl_sds_destroy(f->lval.str);
+
+ TEST_CHECK(lex(f) == '{');
+
+ TEST_CHECK(lex(f) == IDENTIFIER);
+ TEST_CHECK(strcmp(f->lval.str, "escapes") == 0);
+ cfl_sds_destroy(f->lval.str);
+
+ TEST_CHECK(lex(f) == '=');
+
+ TEST_CHECK(lex(f) == QUOTED);
+ TEST_CHECK(strcmp(f->lval.str, "\n \\ \"") == 0);
+ cfl_sds_destroy(f->lval.str);
+
+ TEST_CHECK(lex(f) == '}');
+
+ TEST_CHECK(lex(f) == NUMSTR);
+ TEST_CHECK(strcmp(f->lval.numstr, "4.12") == 0);
+
+ TEST_CHECK(lex(f) == NUMSTR);
+ TEST_CHECK(strcmp(f->lval.numstr, "5") == 0);
+
+ destroy(f);
+}
+
+
+TEST_LIST = {
+ {"test_comment", test_comment},
+ {"test_help", test_help},
+ {"test_type", test_type},
+ {"test_simple", test_simple},
+ { 0 }
+};
diff --git a/src/fluent-bit/lib/cmetrics/tests/prometheus_parser.c b/src/fluent-bit/lib/cmetrics/tests/prometheus_parser.c
new file mode 100644
index 000000000..8cdef466a
--- /dev/null
+++ b/src/fluent-bit/lib/cmetrics/tests/prometheus_parser.c
@@ -0,0 +1,1701 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* CMetrics
+ * ========
+ * Copyright 2021-2022 The CMetrics Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cmetrics/cmetrics.h>
+#include <cmetrics/cmt_map.h>
+#include <cmetrics/cmt_decode_prometheus.h>
+#include <cmetrics/cmt_encode_prometheus.h>
+#include <stdio.h>
+
+#include "cmetrics/cmt_counter.h"
+#include "cmetrics/cmt_summary.h"
+#include "cmt_decode_prometheus_parser.h"
+#include "cmt_tests.h"
+#include "lib/acutest/acutest.h"
+#include "tests/cmt_tests_config.h"
+
+struct fixture {
+ yyscan_t scanner;
+ YY_BUFFER_STATE buf;
+ YYSTYPE lval;
+ struct cmt_decode_prometheus_context context;
+ const char *text;
+};
+
+struct fixture *init(int start_token, const char *test)
+{
+ cmt_initialize();
+ struct fixture *f = malloc(sizeof(*f));
+ memset(f, 0, sizeof(*f));
+ f->context.cmt = cmt_create();
+ f->context.opts.start_token = start_token;
+ cfl_list_init(&(f->context.metric.samples));
+ cmt_decode_prometheus_lex_init(&f->scanner);
+ f->buf = cmt_decode_prometheus__scan_string(test, f->scanner);
+ return f;
+}
+
+void destroy(struct fixture *f)
+{
+ cmt_decode_prometheus__delete_buffer(f->buf, f->scanner);
+ cmt_decode_prometheus_lex_destroy(f->scanner);
+ cmt_destroy(f->context.cmt);
+ free(f);
+}
+
+int parse(struct fixture *f)
+{
+ return cmt_decode_prometheus_parse(f->scanner, &f->context);
+}
+
+void test_header_help()
+{
+ struct fixture *f = init(START_HEADER,
+ "# HELP cmt_labels_test Static labels test\n"
+ );
+
+ TEST_CHECK(parse(f) == 0);
+
+ TEST_CHECK(strcmp(f->context.metric.ns, "cmt") == 0);
+ TEST_CHECK(strcmp(f->context.metric.subsystem, "labels") == 0);
+ TEST_CHECK(strcmp(f->context.metric.name, "test") == 0);
+ TEST_CHECK(strcmp(f->context.metric.docstring, "Static labels test") == 0);
+ TEST_CHECK(f->context.metric.type == 0);
+ cfl_sds_destroy(f->context.metric.name_orig);
+ cfl_sds_destroy(f->context.metric.docstring);
+ free(f->context.metric.ns);
+
+ destroy(f);
+}
+
+void test_header_type()
+{
+ struct fixture *f = init(START_HEADER,
+ "# TYPE cmt_labels_test counter\n"
+ );
+ TEST_CHECK(parse(f) == 0);
+
+ TEST_CHECK(strcmp(f->context.metric.ns, "cmt") == 0);
+ TEST_CHECK(strcmp(f->context.metric.subsystem, "labels") == 0);
+ TEST_CHECK(strcmp(f->context.metric.name, "test") == 0);
+ TEST_CHECK(f->context.metric.type == COUNTER);
+ TEST_CHECK(f->context.metric.docstring == NULL);
+ cfl_sds_destroy(f->context.metric.name_orig);
+ free(f->context.metric.ns);
+
+ destroy(f);
+}
+
+void test_header_help_type()
+{
+ struct fixture *f = init(START_HEADER,
+ "# HELP cmt_labels_test Static labels test\n"
+ "# TYPE cmt_labels_test summary\n"
+ );
+
+ TEST_CHECK(parse(f) == 0);
+
+ TEST_CHECK(strcmp(f->context.metric.docstring, "Static labels test") == 0);
+ TEST_CHECK(strcmp(f->context.metric.ns, "cmt") == 0);
+ TEST_CHECK(strcmp(f->context.metric.subsystem, "labels") == 0);
+ TEST_CHECK(strcmp(f->context.metric.name, "test") == 0);
+ TEST_CHECK(f->context.metric.type == SUMMARY);
+ cfl_sds_destroy(f->context.metric.name_orig);
+ cfl_sds_destroy(f->context.metric.docstring);
+ free(f->context.metric.ns);
+
+ destroy(f);
+}
+
+void test_header_type_help()
+{
+ struct fixture *f = init(START_HEADER,
+ "# TYPE cmt_labels_test gauge\n"
+ "# HELP cmt_labels_test Static labels test\n"
+ );
+
+ TEST_CHECK(parse(f) == 0);
+
+ TEST_CHECK(strcmp(f->context.metric.docstring, "Static labels test") == 0);
+ TEST_CHECK(strcmp(f->context.metric.ns, "cmt") == 0);
+ TEST_CHECK(strcmp(f->context.metric.subsystem, "labels") == 0);
+ TEST_CHECK(strcmp(f->context.metric.name, "test") == 0);
+ TEST_CHECK(f->context.metric.type == GAUGE);
+ cfl_sds_destroy(f->context.metric.name_orig);
+ cfl_sds_destroy(f->context.metric.docstring);
+ free(f->context.metric.ns);
+
+ destroy(f);
+}
+
+struct cmt_decode_prometheus_context_sample *add_empty_sample(struct fixture *f)
+{
+ struct cmt_decode_prometheus_context_sample *sample;
+ sample = malloc(sizeof(*sample));
+ memset(sample, 0, sizeof(*sample));
+ cfl_list_add(&sample->_head, &f->context.metric.samples);
+ return sample;
+}
+
+void test_labels()
+{
+ struct fixture *f = init(START_LABELS, "dev=\"Calyptia\",lang=\"C\"");
+ struct cmt_decode_prometheus_context_sample *sample = add_empty_sample(f);
+ TEST_CHECK(parse(f) == 0);
+ TEST_CHECK(f->context.metric.label_count == 2);
+ TEST_CHECK(strcmp(f->context.metric.labels[0], "dev") == 0);
+ TEST_CHECK(strcmp(sample->label_values[0], "Calyptia") == 0);
+ TEST_CHECK(strcmp(f->context.metric.labels[1], "lang") == 0);
+ TEST_CHECK(strcmp(sample->label_values[1], "C") == 0);
+ cfl_sds_destroy(f->context.metric.labels[0]);
+ cfl_sds_destroy(sample->label_values[0]);
+ cfl_sds_destroy(f->context.metric.labels[1]);
+ cfl_sds_destroy(sample->label_values[1]);
+ free(sample);
+ destroy(f);
+}
+
+void test_labels_trailing_comma()
+{
+ struct fixture *f = init(START_LABELS, "dev=\"Calyptia\",lang=\"C\",");
+ struct cmt_decode_prometheus_context_sample *sample = add_empty_sample(f);
+ TEST_CHECK(parse(f) == 0);
+ TEST_CHECK(f->context.metric.label_count == 2);
+ TEST_CHECK(strcmp(f->context.metric.labels[0], "dev") == 0);
+ TEST_CHECK(strcmp(sample->label_values[0], "Calyptia") == 0);
+ TEST_CHECK(strcmp(f->context.metric.labels[1], "lang") == 0);
+ TEST_CHECK(strcmp(sample->label_values[1], "C") == 0);
+ cfl_sds_destroy(f->context.metric.labels[0]);
+ cfl_sds_destroy(sample->label_values[0]);
+ cfl_sds_destroy(f->context.metric.labels[1]);
+ cfl_sds_destroy(sample->label_values[1]);
+ free(sample);
+ destroy(f);
+}
+
+void test_sample()
+{
+ cfl_sds_t result;
+ const char expected[] = (
+ "# HELP cmt_labels_test some docstring\n"
+ "# TYPE cmt_labels_test counter\n"
+ "cmt_labels_test{dev=\"Calyptia\",lang=\"C\"} 1 0\n"
+ );
+
+ struct fixture *f = init(0,
+ "# HELP cmt_labels_test some docstring\n"
+ "# TYPE cmt_labels_test counter\n"
+ "cmt_labels_test{dev=\"Calyptia\",lang=\"C\",} 1 0\n"
+ );
+
+ TEST_CHECK(parse(f) == 0);
+ result = cmt_encode_prometheus_create(f->context.cmt, CMT_TRUE);
+ TEST_CHECK(strcmp(result, expected) == 0);
+ cfl_sds_destroy(result);
+
+ destroy(f);
+}
+
+void test_samples()
+{
+ cfl_sds_t result;
+ const char expected[] = (
+ "# HELP cmt_labels_test some docstring\n"
+ "# TYPE cmt_labels_test gauge\n"
+ "cmt_labels_test{dev=\"Calyptia\",lang=\"C\"} 5 999999\n"
+ "cmt_labels_test{dev=\"Calyptia\",lang=\"C++\"} 6 7777\n"
+
+ );
+
+ struct fixture *f = init(0,
+ "# HELP cmt_labels_test some docstring\n"
+ "# TYPE cmt_labels_test gauge\n"
+ "cmt_labels_test{dev=\"Calyptia\",lang=\"C\",} 5 999999\n"
+ "cmt_labels_test{dev=\"Calyptia\",lang=\"C++\"} 6 7777\n"
+ );
+
+ TEST_CHECK(parse(f) == 0);
+ result = cmt_encode_prometheus_create(f->context.cmt, CMT_TRUE);
+ TEST_CHECK(strcmp(result, expected) == 0);
+ cfl_sds_destroy(result);
+
+ destroy(f);
+}
+
+void test_escape_sequences()
+{
+ cfl_sds_t result;
+ const char expected[] = (
+ "# HELP msdos_file_access_time_seconds\n"
+ "# TYPE msdos_file_access_time_seconds untyped\n"
+ "msdos_file_access_time_seconds{path=\"C:\\\\DIR\\\\FILE.TXT\",error=\"Cannot find file:\\n\\\"FILE.TXT\\\"\"} 1458255915 0\n"
+ );
+
+ struct fixture *f = init(0,
+ "# Escaping in label values:\n"
+ "msdos_file_access_time_seconds{path=\"C:\\\\DIR\\\\FILE.TXT\",error=\"Cannot find file:\\n\\\"FILE.TXT\\\"\"} 1.458255915e9\n"
+ );
+
+ TEST_CHECK(parse(f) == 0);
+ result = cmt_encode_prometheus_create(f->context.cmt, CMT_TRUE);
+ TEST_CHECK(strcmp(result, expected) == 0);
+ cfl_sds_destroy(result);
+
+ destroy(f);
+}
+
+void test_metric_without_labels()
+{
+ cfl_sds_t result;
+
+ const char expected[] =
+ "# HELP metric_without_timestamp_and_labels\n"
+ "# TYPE metric_without_timestamp_and_labels untyped\n"
+ "metric_without_timestamp_and_labels 12.470000000000001 0\n"
+ ;
+
+ struct fixture *f = init(0,
+ "# Minimalistic line:\n"
+ "metric_without_timestamp_and_labels 12.47\n"
+ );
+
+ TEST_CHECK(parse(f) == 0);
+ result = cmt_encode_prometheus_create(f->context.cmt, CMT_TRUE);
+ TEST_CHECK(strcmp(result, expected) == 0);
+ cfl_sds_destroy(result);
+
+ destroy(f);
+}
+
+void test_prometheus_spec_example()
+{
+ char errbuf[256];
+ int status;
+ cfl_sds_t result;
+ struct cmt *cmt;
+ struct cmt_decode_prometheus_parse_opts opts;
+ memset(&opts, 0, sizeof(opts));
+ opts.errbuf = errbuf;
+ opts.errbuf_size = sizeof(errbuf);
+ const char in_buf[] =
+ "# TYPE http_requests_total counter\n"
+ "# HELP http_requests_total The total number of HTTP requests.\n"
+ "http_requests_total{method=\"post\",code=\"200\"} 1027 1395066363000\n"
+ "http_requests_total{method=\"post\",code=\"400\"} 3 1395066363000\n"
+ "\n"
+ "# Escaping in label values:\n"
+ "msdos_file_access_time_seconds{path=\"C:\\\\DIR\\\\FILE.TXT\",error=\"Cannot find file:\\n\\\"FILE.TXT\\\"\"} 1.458255915e9\n"
+ "\n"
+ "# Minimalistic line:\n"
+ "metric_without_timestamp_and_labels 12.47\n"
+ "\n"
+ "# A weird metric from before the epoch:\n"
+ "something_weird{problem=\"division by zero\"} +Inf -3982045\n"
+ "\n"
+ "# A histogram, which has a pretty complex representation in the text format:\n"
+ "# HELP http_request_duration_seconds_bucket A histogram of the request duration.\n"
+ "# TYPE http_request_duration_seconds_bucket counter\n"
+ "http_request_duration_seconds_bucket{le=\"0.05\"} 24054\n"
+ "http_request_duration_seconds_bucket{le=\"0.1\"} 33444\n"
+ "http_request_duration_seconds_bucket{le=\"0.2\"} 100392\n"
+ "http_request_duration_seconds_bucket{le=\"0.5\"} 129389\n"
+ "http_request_duration_seconds_bucket{le=\"1\"} 133988\n"
+ "http_request_duration_seconds_bucket{le=\"+Inf\"} 144320\n"
+ "http_request_duration_seconds_sum 53423\n"
+ "http_request_duration_seconds_count 144320\n"
+ "\n"
+ "# Finally a summary, which has a complex representation, too:\n"
+ "# HELP rpc_duration_seconds A summary of the RPC duration in seconds.\n"
+ "# TYPE rpc_duration_seconds gauge\n"
+ "rpc_duration_seconds{quantile=\"0.01\"} 3102\n"
+ "rpc_duration_seconds{quantile=\"0.05\"} 3272\n"
+ "rpc_duration_seconds{quantile=\"0.5\"} 4773\n"
+ "rpc_duration_seconds{quantile=\"0.9\"} 9001\n"
+ "rpc_duration_seconds{quantile=\"0.99\"} 76656\n"
+ "rpc_duration_seconds_sum 1.7560473e+07\n"
+ "rpc_duration_seconds_count 2693\n"
+ ;
+ const char expected[] =
+ "# HELP http_requests_total The total number of HTTP requests.\n"
+ "# TYPE http_requests_total counter\n"
+ "http_requests_total{method=\"post\",code=\"200\"} 1027 1395066363000\n"
+ "http_requests_total{method=\"post\",code=\"400\"} 3 1395066363000\n"
+ "# HELP http_request_duration_seconds_bucket A histogram of the request duration.\n"
+ "# TYPE http_request_duration_seconds_bucket counter\n"
+ "http_request_duration_seconds_bucket{le=\"0.05\"} 24054 0\n"
+ "http_request_duration_seconds_bucket{le=\"0.1\"} 33444 0\n"
+ "http_request_duration_seconds_bucket{le=\"0.2\"} 100392 0\n"
+ "http_request_duration_seconds_bucket{le=\"0.5\"} 129389 0\n"
+ "http_request_duration_seconds_bucket{le=\"1\"} 133988 0\n"
+ "http_request_duration_seconds_bucket{le=\"+Inf\"} 144320 0\n"
+ "# HELP rpc_duration_seconds A summary of the RPC duration in seconds.\n"
+ "# TYPE rpc_duration_seconds gauge\n"
+ "rpc_duration_seconds{quantile=\"0.01\"} 3102 0\n"
+ "rpc_duration_seconds{quantile=\"0.05\"} 3272 0\n"
+ "rpc_duration_seconds{quantile=\"0.5\"} 4773 0\n"
+ "rpc_duration_seconds{quantile=\"0.9\"} 9001 0\n"
+ "rpc_duration_seconds{quantile=\"0.99\"} 76656 0\n"
+ "# HELP msdos_file_access_time_seconds\n"
+ "# TYPE msdos_file_access_time_seconds untyped\n"
+ "msdos_file_access_time_seconds{path=\"C:\\\\DIR\\\\FILE.TXT\",error=\"Cannot find file:\\n\\\"FILE.TXT\\\"\"} 1458255915 0\n"
+ "# HELP metric_without_timestamp_and_labels\n"
+ "# TYPE metric_without_timestamp_and_labels untyped\n"
+ "metric_without_timestamp_and_labels 12.470000000000001 0\n"
+ "# HELP something_weird\n"
+ "# TYPE something_weird untyped\n"
+ "something_weird{problem=\"division by zero\"} inf 0\n"
+ "# HELP http_request_duration_seconds_sum\n"
+ "# TYPE http_request_duration_seconds_sum untyped\n"
+ "http_request_duration_seconds_sum 53423 0\n"
+ "# HELP http_request_duration_seconds_count\n"
+ "# TYPE http_request_duration_seconds_count untyped\n"
+ "http_request_duration_seconds_count 144320 0\n"
+ "# HELP rpc_duration_seconds_sum\n"
+ "# TYPE rpc_duration_seconds_sum untyped\n"
+ "rpc_duration_seconds_sum 17560473 0\n"
+ "# HELP rpc_duration_seconds_count\n"
+ "# TYPE rpc_duration_seconds_count untyped\n"
+ "rpc_duration_seconds_count 2693 0\n"
+ ;
+
+ cmt_initialize();
+ status = cmt_decode_prometheus_create(&cmt, in_buf, 0, &opts);
+ TEST_CHECK(status == 0);
+ result = cmt_encode_prometheus_create(cmt, CMT_TRUE);
+ TEST_CHECK(strcmp(result, expected) == 0);
+ cfl_sds_destroy(result);
+ cmt_decode_prometheus_destroy(cmt);
+}
+
+void test_bison_parsing_error()
+{
+ // Note that in this test I commented checks for the error message. The
+ // reason is that the message is different depending on which bison
+ // version is used to generate the parser, so not fully deterministic.
+ int status;
+ char errbuf[256];
+ struct cmt *cmt;
+ struct cmt_decode_prometheus_parse_opts opts;
+ memset(&opts, 0, sizeof(opts));
+ opts.errbuf = errbuf;
+ opts.errbuf_size = sizeof(errbuf);
+
+ status = cmt_decode_prometheus_create(&cmt, "", 0, &opts);
+ TEST_CHECK(status == CMT_DECODE_PROMETHEUS_SYNTAX_ERROR);
+ // TEST_CHECK(strcmp(errbuf,
+ // "syntax error, unexpected end of file") == 0);
+
+ status = cmt_decode_prometheus_create(&cmt,
+ "# HELP metric_name some docstring\n"
+ "# TYPE metric_name counter\n"
+ "metric_name", 0, &opts);
+ TEST_CHECK(status == CMT_DECODE_PROMETHEUS_SYNTAX_ERROR);
+ // TEST_CHECK(strcmp(errbuf,
+ // "syntax error, unexpected end of file, expecting '{' "
+ // "or FPOINT or INTEGER") == 0);
+
+ status = cmt_decode_prometheus_create(&cmt,
+ "# HELP metric_name some docstring\n"
+ "# TYPE metric_name counter\n"
+ "metric_name {key", 0, &opts);
+ TEST_CHECK(status == CMT_DECODE_PROMETHEUS_SYNTAX_ERROR);
+ // TEST_CHECK(strcmp(errbuf,
+ // "syntax error, unexpected end of file, expecting '='") == 0);
+
+ status = cmt_decode_prometheus_create(&cmt,
+ "# HELP metric_name some docstring\n"
+ "# TYPE metric_name counter\n"
+ "metric_name {key=", 0, &opts);
+ TEST_CHECK(status == CMT_DECODE_PROMETHEUS_SYNTAX_ERROR);
+ // TEST_CHECK(strcmp(errbuf,
+ // "syntax error, unexpected end of file, expecting QUOTED") == 0);
+
+ status = cmt_decode_prometheus_create(&cmt,
+ "# HELP metric_name some docstring\n"
+ "# TYPE metric_name counter\n"
+ "metric_name {key=\"abc\"", 0, &opts);
+ TEST_CHECK(status == CMT_DECODE_PROMETHEUS_SYNTAX_ERROR);
+ // TEST_CHECK(strcmp(errbuf,
+ // "syntax error, unexpected end of file, expecting '}'") == 0);
+
+ status = cmt_decode_prometheus_create(&cmt,
+ "# HELP metric_name some docstring\n"
+ "# TYPE metric_name counter\n"
+ "metric_name {key=\"abc\"}", 0, &opts);
+ TEST_CHECK(status == CMT_DECODE_PROMETHEUS_SYNTAX_ERROR);
+ // TEST_CHECK(strcmp(errbuf,
+ // "syntax error, unexpected end of file, expecting "
+ // "FPOINT or INTEGER") == 0);
+}
+
+void test_label_limits()
+{
+ int i;
+ int status;
+ struct cmt_counter *counter;
+ char errbuf[256];
+ struct cmt *cmt;
+ char inbuf[65535];
+ int pos;
+ struct cmt_decode_prometheus_parse_opts opts;
+ memset(&opts, 0, sizeof(opts));
+ opts.errbuf = errbuf;
+ opts.errbuf_size = sizeof(errbuf);
+
+ pos = snprintf(inbuf, sizeof(inbuf),
+ "# HELP many_labels_metric reaches maximum number labels\n"
+ "# TYPE many_labels_metric counter\n"
+ "many_labels_metric {");
+ for (i = 0; i < CMT_DECODE_PROMETHEUS_MAX_LABEL_COUNT && pos < sizeof(inbuf); i++) {
+ pos += snprintf(inbuf + pos, sizeof(inbuf) - pos, "l%d=\"%d\",", i, i);
+ }
+ snprintf(inbuf + pos, sizeof(inbuf) - pos, "} 55 0\n");
+
+ status = cmt_decode_prometheus_create(&cmt, inbuf, 0, &opts);
+ TEST_CHECK(status == 0);
+ counter = cfl_list_entry_first(&cmt->counters, struct cmt_counter, _head);
+ TEST_CHECK(counter->map->label_count == CMT_DECODE_PROMETHEUS_MAX_LABEL_COUNT);
+ cmt_decode_prometheus_destroy(cmt);
+
+ // write one more label to exceed limit
+ snprintf(inbuf + pos, sizeof(inbuf) - pos, "last=\"val\"} 55 0\n");
+ status = cmt_decode_prometheus_create(&cmt, inbuf, 0, &opts);
+ TEST_CHECK(status == CMT_DECODE_PROMETHEUS_MAX_LABEL_COUNT_EXCEEDED);
+ TEST_CHECK(strcmp(errbuf, "maximum number of labels exceeded") == 0);
+}
+
+void test_invalid_value()
+{
+ int status;
+ char errbuf[256];
+ struct cmt *cmt;
+ struct cmt_decode_prometheus_parse_opts opts;
+ memset(&opts, 0, sizeof(opts));
+ opts.errbuf = errbuf;
+ opts.errbuf_size = sizeof(errbuf);
+
+ status = cmt_decode_prometheus_create(&cmt,
+ "# HELP metric_name some docstring\n"
+ "# TYPE metric_name counter\n"
+ "metric_name {key=\"abc\"} 10e", 0, &opts);
+ TEST_CHECK(status == CMT_DECODE_PROMETHEUS_PARSE_VALUE_FAILED);
+ TEST_CHECK(strcmp(errbuf,
+ "failed to parse sample: \"10e\" is not a valid value") == 0);
+}
+
+void test_invalid_timestamp()
+{
+ int status;
+ char errbuf[256];
+ struct cmt *cmt;
+ struct cmt_decode_prometheus_parse_opts opts;
+ memset(&opts, 0, sizeof(opts));
+ opts.errbuf = errbuf;
+ opts.errbuf_size = sizeof(errbuf);
+
+ status = cmt_decode_prometheus_create(&cmt,
+ "# HELP metric_name some docstring\n"
+ "# TYPE metric_name counter\n"
+ "metric_name {key=\"abc\"} 10 3e", 0, &opts);
+ TEST_CHECK(status == CMT_DECODE_PROMETHEUS_PARSE_TIMESTAMP_FAILED);
+ TEST_CHECK(strcmp(errbuf,
+ "failed to parse sample: \"3e\" is not a valid timestamp") == 0);
+}
+
+void test_default_timestamp()
+{
+ int status;
+ cfl_sds_t result;
+ struct cmt *cmt;
+ struct cmt_decode_prometheus_parse_opts opts;
+ memset(&opts, 0, sizeof(opts));
+ opts.default_timestamp = 557 * 10e5;
+
+
+ status = cmt_decode_prometheus_create(&cmt,
+ "# HELP metric_name some docstring\n"
+ "# TYPE metric_name counter\n"
+ "metric_name {key=\"abc\"} 10", 0, &opts);
+ TEST_CHECK(status == 0);
+ result = cmt_encode_prometheus_create(cmt, CMT_TRUE);
+ TEST_CHECK(strcmp(result,
+ "# HELP metric_name some docstring\n"
+ "# TYPE metric_name counter\n"
+ "metric_name{key=\"abc\"} 10 557\n") == 0);
+ cfl_sds_destroy(result);
+ cmt_decode_prometheus_destroy(cmt);
+}
+
+void test_values()
+{
+ int status = 0;
+ cfl_sds_t result = NULL;
+ struct cmt *cmt;
+
+ const char expected[] = "# HELP metric_name some docstring\n"
+ "# TYPE metric_name gauge\n"
+ "metric_name{key=\"simple integer\"} 54 0\n"
+ "metric_name{key=\"simple float\"} 12.470000000000001 0\n"
+ "metric_name{key=\"scientific notation 1\"} 17560473 0\n"
+ "metric_name{key=\"scientific notation 2\"} 1.7560473000000001 0\n"
+ "metric_name{key=\"Positive \\\"not a number\\\"\"} nan 0\n"
+ "metric_name{key=\"Positive infinity\"} inf 0\n"
+ "metric_name{key=\"Negative infinity\"} -inf 0\n";
+
+ status = cmt_decode_prometheus_create(&cmt,
+ "# HELP metric_name some docstring\n"
+ "# TYPE metric_name gauge\n"
+ "metric_name {key=\"simple integer\"} 54\n"
+ "metric_name {key=\"simple float\"} 12.47\n"
+ "metric_name {key=\"scientific notation 1\"} 1.7560473e+07\n"
+ "metric_name {key=\"scientific notation 2\"} 17560473e-07\n"
+ "metric_name {key=\"Positive \\\"not a number\\\"\"} +NAN\n"
+ "metric_name {key=\"Positive infinity\"} +INF\n"
+ "metric_name {key=\"Negative infinity\"} -iNf\n", 0, NULL);
+ TEST_CHECK(status == 0);
+ if (!status) {
+ result = cmt_encode_prometheus_create(cmt, CMT_TRUE);
+ status = strcmp(result, expected);
+ TEST_CHECK(status == 0);
+ if (status) {
+ fprintf(stderr, "EXPECTED:\n======\n%s\n======\nRESULT:\n======\n%s\n======\n", expected, result);
+ }
+ }
+
+ cfl_sds_destroy(result);
+ cmt_decode_prometheus_destroy(cmt);
+}
+
+void test_in_size()
+{
+ int status;
+ cfl_sds_t result;
+ struct cmt *cmt;
+ cfl_sds_t in_buf;
+ size_t in_size;
+
+ in_buf = cfl_sds_create("metric_name {key=\"1\"} 1\n");
+ in_size = cfl_sds_len(in_buf);
+ in_buf = cfl_sds_cat(in_buf, "metric_name {key=\"2\"} 2\n", in_size);
+
+ status = cmt_decode_prometheus_create(&cmt, in_buf, in_size, NULL);
+ TEST_CHECK(status == 0);
+ result = cmt_encode_prometheus_create(cmt, CMT_TRUE);
+ TEST_CHECK(strcmp(result,
+ "# HELP metric_name\n"
+ "# TYPE metric_name untyped\n"
+ "metric_name{key=\"1\"} 1 0\n") == 0);
+ cfl_sds_destroy(result);
+ cfl_sds_destroy(in_buf);
+ cmt_decode_prometheus_destroy(cmt);
+}
+
+// reproduces https://github.com/calyptia/cmetrics/issues/71
+void test_issue_71()
+{
+ int status;
+ struct cmt *cmt;
+ cfl_sds_t in_buf = read_file(CMT_TESTS_DATA_PATH "/issue_71.txt");
+ size_t in_size = cfl_sds_len(in_buf);
+
+ status = cmt_decode_prometheus_create(&cmt, in_buf, in_size, NULL);
+ TEST_CHECK(status == 0);
+ cfl_sds_destroy(in_buf);
+ cmt_decode_prometheus_destroy(cmt);
+}
+
+void test_histogram()
+{
+ int status;
+ struct cmt *cmt;
+ struct cmt_decode_prometheus_parse_opts opts;
+ cfl_sds_t result;
+ memset(&opts, 0, sizeof(opts));
+
+ status = cmt_decode_prometheus_create(&cmt,
+ "# HELP http_request_duration_seconds A histogram of the request duration.\n"
+ "# TYPE http_request_duration_seconds histogram\n"
+ "http_request_duration_seconds_bucket{le=\"0.05\"} 24054\n"
+ "http_request_duration_seconds_bucket{le=\"0.1\"} 33444\n"
+ "http_request_duration_seconds_bucket{le=\"0.2\"} 100392\n"
+ "http_request_duration_seconds_bucket{le=\"0.5\"} 129389\n"
+ "http_request_duration_seconds_bucket{le=\"1\"} 133988\n"
+ "http_request_duration_seconds_bucket{le=\"+Inf\"} 144320\n"
+ "http_request_duration_seconds_sum 53423\n"
+ "http_request_duration_seconds_count 144320\n", 0, &opts);
+ TEST_CHECK(status == 0);
+ result = cmt_encode_prometheus_create(cmt, CMT_FALSE);
+ TEST_CHECK(strcmp(result,
+ "# HELP http_request_duration_seconds A histogram of the request duration.\n"
+ "# TYPE http_request_duration_seconds histogram\n"
+ "http_request_duration_seconds_bucket{le=\"0.05\"} 24054\n"
+ "http_request_duration_seconds_bucket{le=\"0.1\"} 33444\n"
+ "http_request_duration_seconds_bucket{le=\"0.2\"} 100392\n"
+ "http_request_duration_seconds_bucket{le=\"0.5\"} 129389\n"
+ "http_request_duration_seconds_bucket{le=\"1.0\"} 133988\n"
+ "http_request_duration_seconds_bucket{le=\"+Inf\"} 144320\n"
+ "http_request_duration_seconds_sum 53423\n"
+ "http_request_duration_seconds_count 144320\n") == 0);
+ cfl_sds_destroy(result);
+ cmt_decode_prometheus_destroy(cmt);
+}
+
+void test_histogram_labels()
+{
+ int status;
+ struct cmt *cmt;
+ struct cmt_decode_prometheus_parse_opts opts;
+ memset(&opts, 0, sizeof(opts));
+ cfl_sds_t result;
+
+ status = cmt_decode_prometheus_create(&cmt,
+ "# HELP http_request_duration_seconds A histogram of the request duration.\n"
+ "# TYPE http_request_duration_seconds histogram\n"
+ "http_request_duration_seconds_bucket{label1=\"val1\",le=\"0.05\",label2=\"val2\"} 24054\n"
+ "http_request_duration_seconds_bucket{label1=\"val1\",le=\"0.1\",label2=\"val2\"} 33444\n"
+ "http_request_duration_seconds_bucket{label1=\"val1\",le=\"0.2\",label2=\"val2\"} 100392\n"
+ "http_request_duration_seconds_bucket{label1=\"val1\",le=\"0.5\",label2=\"val2\"} 129389\n"
+ "http_request_duration_seconds_bucket{label1=\"val1\",le=\"1\",label2=\"val2\"} 133988\n"
+ "http_request_duration_seconds_bucket{label1=\"val1\",le=\"+Inf\",label2=\"val2\"} 144320\n"
+ "http_request_duration_seconds_sum{label1=\"val1\",label2=\"val2\"} 53423\n"
+ "http_request_duration_seconds_count{label1=\"val1\",label2=\"val2\"}144320\n", 0, &opts);
+ TEST_CHECK(status == 0);
+ result = cmt_encode_prometheus_create(cmt, CMT_FALSE);
+ TEST_CHECK(strcmp(result,
+ "# HELP http_request_duration_seconds A histogram of the request duration.\n"
+ "# TYPE http_request_duration_seconds histogram\n"
+ "http_request_duration_seconds_bucket{le=\"0.05\",label1=\"val1\",label2=\"val2\"} 24054\n"
+ "http_request_duration_seconds_bucket{le=\"0.1\",label1=\"val1\",label2=\"val2\"} 33444\n"
+ "http_request_duration_seconds_bucket{le=\"0.2\",label1=\"val1\",label2=\"val2\"} 100392\n"
+ "http_request_duration_seconds_bucket{le=\"0.5\",label1=\"val1\",label2=\"val2\"} 129389\n"
+ "http_request_duration_seconds_bucket{le=\"1.0\",label1=\"val1\",label2=\"val2\"} 133988\n"
+ "http_request_duration_seconds_bucket{le=\"+Inf\",label1=\"val1\",label2=\"val2\"} 144320\n"
+ "http_request_duration_seconds_sum{label1=\"val1\",label2=\"val2\"} 53423\n"
+ "http_request_duration_seconds_count{label1=\"val1\",label2=\"val2\"} 144320\n") == 0);
+ cfl_sds_destroy(result);
+ cmt_decode_prometheus_destroy(cmt);
+}
+
+void test_summary()
+{
+ int status;
+ struct cmt *cmt;
+ struct cmt_decode_prometheus_parse_opts opts;
+ cfl_sds_t result;
+ memset(&opts, 0, sizeof(opts));
+
+ status = cmt_decode_prometheus_create(&cmt,
+ "# HELP rpc_duration_seconds A summary of the RPC duration in seconds.\n"
+ "# TYPE rpc_duration_seconds summary\n"
+ "rpc_duration_seconds{quantile=\"0.01\"} 3102\n"
+ "rpc_duration_seconds{quantile=\"0.05\"} 3272\n"
+ "rpc_duration_seconds{quantile=\"0.5\"} 4773\n"
+ "rpc_duration_seconds{quantile=\"0.9\"} 9001\n"
+ "rpc_duration_seconds{quantile=\"0.99\"} 76656\n"
+ "rpc_duration_seconds_sum 1.7560473e+07\n"
+ "rpc_duration_seconds_count 2693\n", 0, &opts);
+ TEST_CHECK(status == 0);
+ result = cmt_encode_prometheus_create(cmt, CMT_FALSE);
+ TEST_CHECK(strcmp(result,
+ "# HELP rpc_duration_seconds A summary of the RPC duration in seconds.\n"
+ "# TYPE rpc_duration_seconds summary\n"
+ "rpc_duration_seconds{quantile=\"0.01\"} 3102\n"
+ "rpc_duration_seconds{quantile=\"0.05\"} 3272\n"
+ "rpc_duration_seconds{quantile=\"0.5\"} 4773\n"
+ "rpc_duration_seconds{quantile=\"0.9\"} 9001\n"
+ "rpc_duration_seconds{quantile=\"0.99\"} 76656\n"
+ "rpc_duration_seconds_sum 17560473\n"
+ "rpc_duration_seconds_count 2693\n") == 0);
+ cfl_sds_destroy(result);
+ cmt_decode_prometheus_destroy(cmt);
+}
+
+void test_null_labels()
+{
+ int status;
+ cfl_sds_t result;
+ struct cmt *cmt;
+ struct cmt_decode_prometheus_parse_opts opts;
+ memset(&opts, 0, sizeof(opts));
+ const char in_buf[] =
+ "# TYPE ns_ss_name counter\n"
+ "# HELP ns_ss_name Example with null labels.\n"
+ "ns_ss_name{A=\"a\",B=\"b\",C=\"c\"} 1027 1395066363000\n"
+ "ns_ss_name{C=\"c\",D=\"d\",E=\"e\"} 1027 1395066363000\n"
+ ;
+ const char expected[] =
+ "# HELP ns_ss_name Example with null labels.\n"
+ "# TYPE ns_ss_name counter\n"
+ "ns_ss_name{A=\"a\",B=\"b\",C=\"c\"} 1027 1395066363000\n"
+ "ns_ss_name{C=\"c\",D=\"d\",E=\"e\"} 1027 1395066363000\n"
+ ;
+
+ cmt_initialize();
+ status = cmt_decode_prometheus_create(&cmt, in_buf, 0, &opts);
+ TEST_CHECK(status == 0);
+ result = cmt_encode_prometheus_create(cmt, CMT_TRUE);
+ TEST_CHECK(strcmp(result, expected) == 0);
+ cfl_sds_destroy(result);
+ cmt_decode_prometheus_destroy(cmt);
+}
+
+// reproduces https://github.com/fluent/fluent-bit/issues/5541
+void test_issue_fluent_bit_5541()
+{
+ int status;
+ char *result;
+ struct cmt *cmt;
+ cfl_sds_t in_buf = read_file(CMT_TESTS_DATA_PATH "/issue_fluent_bit_5541.txt");
+ size_t in_size = cfl_sds_len(in_buf);
+
+ const char expected[] =
+ "# HELP http_request_duration_seconds HTTP request latency (seconds)\n"
+ "# TYPE http_request_duration_seconds histogram\n"
+ "http_request_duration_seconds_bucket{le=\"0.005\"} 2 0\n"
+ "http_request_duration_seconds_bucket{le=\"0.01\"} 2 0\n"
+ "http_request_duration_seconds_bucket{le=\"0.025\"} 2 0\n"
+ "http_request_duration_seconds_bucket{le=\"0.05\"} 2 0\n"
+ "http_request_duration_seconds_bucket{le=\"0.075\"} 2 0\n"
+ "http_request_duration_seconds_bucket{le=\"0.1\"} 2 0\n"
+ "http_request_duration_seconds_bucket{le=\"0.25\"} 2 0\n"
+ "http_request_duration_seconds_bucket{le=\"0.5\"} 2 0\n"
+ "http_request_duration_seconds_bucket{le=\"0.75\"} 2 0\n"
+ "http_request_duration_seconds_bucket{le=\"1.0\"} 2 0\n"
+ "http_request_duration_seconds_bucket{le=\"2.5\"} 2 0\n"
+ "http_request_duration_seconds_bucket{le=\"5.0\"} 2 0\n"
+ "http_request_duration_seconds_bucket{le=\"7.5\"} 2 0\n"
+ "http_request_duration_seconds_bucket{le=\"10.0\"} 2 0\n"
+ "http_request_duration_seconds_bucket{le=\"+Inf\"} 2 0\n"
+ "http_request_duration_seconds_sum 0.00069131026975810528 0\n"
+ "http_request_duration_seconds_count 2 0\n"
+ ;
+
+ status = cmt_decode_prometheus_create(&cmt, in_buf, in_size, NULL);
+ TEST_CHECK(status == 0);
+
+ result = cmt_encode_prometheus_create(cmt, CMT_TRUE);
+ TEST_CHECK(strcmp(result, expected) == 0);
+
+ cfl_sds_destroy(in_buf);
+ cfl_sds_destroy(result);
+ cmt_decode_prometheus_destroy(cmt);
+}
+
+// reproduces https://github.com/fluent/fluent-bit/issues/5894
+void test_issue_fluent_bit_5894()
+{
+ char errbuf[256];
+ int status;
+ cfl_sds_t result = NULL;
+ struct cmt *cmt;
+ struct cmt_decode_prometheus_parse_opts opts;
+ memset(&opts, 0, sizeof(opts));
+ opts.errbuf = errbuf;
+ opts.errbuf_size = sizeof(errbuf);
+ cfl_sds_t in_buf = read_file(CMT_TESTS_DATA_PATH "/issue_fluent_bit_5894.txt");
+ size_t in_size = cfl_sds_len(in_buf);
+
+ const char expected[] =
+ "# HELP hikaricp_connections_timeout_total Connection timeout total count\n"
+ "# TYPE hikaricp_connections_timeout_total counter\n"
+ "hikaricp_connections_timeout_total{pool=\"mcadb\"} 0 0\n"
+ "# HELP rabbitmq_consumed_total\n"
+ "# TYPE rabbitmq_consumed_total counter\n"
+ "rabbitmq_consumed_total{name=\"rabbit\"} 0 0\n"
+ "# HELP rabbitmq_failed_to_publish_total\n"
+ "# TYPE rabbitmq_failed_to_publish_total counter\n"
+ "rabbitmq_failed_to_publish_total{name=\"rabbit\"} 0 0\n"
+ "# HELP rabbitmq_acknowledged_published_total\n"
+ "# TYPE rabbitmq_acknowledged_published_total counter\n"
+ "rabbitmq_acknowledged_published_total{name=\"rabbit\"} 0 0\n"
+ "# HELP tomcat_sessions_rejected_sessions_total\n"
+ "# TYPE tomcat_sessions_rejected_sessions_total counter\n"
+ "tomcat_sessions_rejected_sessions_total 0 0\n"
+
+ "# HELP process_start_time_seconds Start time of the process since unix epoch.\n"
+ "# TYPE process_start_time_seconds gauge\n"
+ "process_start_time_seconds 1660594096.832 0\n"
+ "# HELP spring_kafka_listener_seconds_max Kafka Listener Timer\n"
+ "# TYPE spring_kafka_listener_seconds_max gauge\n"
+ "spring_kafka_listener_seconds_max{exception=\"ListenerExecutionFailedException\",name=\"org.springframework.kafka.KafkaListenerEndpointContainer#0-0\",result=\"failure\"} 0 0\n"
+ "spring_kafka_listener_seconds_max{exception=\"none\",name=\"org.springframework.kafka.KafkaListenerEndpointContainer#0-0\",result=\"success\"} 0 0\n"
+ "# HELP process_files_max_files The maximum file descriptor count\n"
+ "# TYPE process_files_max_files gauge\n"
+ "process_files_max_files 1048576 0\n"
+ "# HELP hikaricp_connections_pending Pending threads\n"
+ "# TYPE hikaricp_connections_pending gauge\n"
+ "hikaricp_connections_pending{pool=\"mcadb\"} 0 0\n"
+ "# HELP jvm_memory_committed_bytes The amount of memory in bytes that is committed for the Java virtual machine to use\n"
+ "# TYPE jvm_memory_committed_bytes gauge\n"
+ "jvm_memory_committed_bytes{area=\"nonheap\",id=\"CodeHeap 'profiled nmethods'\"} 16056320 0\n"
+ "jvm_memory_committed_bytes{area=\"heap\",id=\"G1 Survivor Space\"} 20971520 0\n"
+ "jvm_memory_committed_bytes{area=\"heap\",id=\"G1 Old Gen\"} 232783872 0\n"
+ "jvm_memory_committed_bytes{area=\"nonheap\",id=\"Metaspace\"} 103374848 0\n"
+ "jvm_memory_committed_bytes{area=\"nonheap\",id=\"CodeHeap 'non-nmethods'\"} 4390912 0\n"
+ "jvm_memory_committed_bytes{area=\"heap\",id=\"G1 Eden Space\"} 373293056 0\n"
+ "jvm_memory_committed_bytes{area=\"nonheap\",id=\"Compressed Class Space\"} 13500416 0\n"
+ "jvm_memory_committed_bytes{area=\"nonheap\",id=\"CodeHeap 'non-profiled nmethods'\"} 4521984 0\n"
+ "# HELP process_files_open_files The open file descriptor count\n"
+ "# TYPE process_files_open_files gauge\n"
+ "process_files_open_files 290 0\n"
+ "# HELP kafka_consumer_sync_time_max_seconds The max time taken for a group sync.\n"
+ "# TYPE kafka_consumer_sync_time_max_seconds gauge\n"
+ "kafka_consumer_sync_time_max_seconds{client_id=\"consumer-1\"} nan 0\n"
+ "# HELP kafka_consumer_fetch_latency_avg_seconds The average time taken for a fetch request.\n"
+ "# TYPE kafka_consumer_fetch_latency_avg_seconds gauge\n"
+ "kafka_consumer_fetch_latency_avg_seconds{client_id=\"consumer-1\"} nan 0\n"
+ "# HELP rabbitmq_channels\n"
+ "# TYPE rabbitmq_channels gauge\n"
+ "rabbitmq_channels{name=\"rabbit\"} 0 0\n"
+ "# HELP kafka_consumer_sync_rate_syncs The number of group syncs per second. Group synchronization is the second and last phase of the rebalance protocol. A large value indicates group instability.\n"
+ "# TYPE kafka_consumer_sync_rate_syncs gauge\n"
+ "kafka_consumer_sync_rate_syncs{client_id=\"consumer-1\"} 0 0\n"
+ "# HELP jvm_classes_loaded_classes The number of classes that are currently loaded in the Java virtual machine\n"
+ "# TYPE jvm_classes_loaded_classes gauge\n"
+ "jvm_classes_loaded_classes 17220 0\n"
+ "# HELP jdbc_connections_min\n"
+ "# TYPE jdbc_connections_min gauge\n"
+ "jdbc_connections_min{name=\"dataSource\"} 10 0\n"
+ "# HELP kafka_consumer_fetch_throttle_time_avg_seconds The average throttle time. When quotas are enabled, the broker may delay fetch requests in order to throttle a consumer which has exceeded its limit. This metric indicates how throttling time has been added to fetch requests on average.\n"
+ "# TYPE kafka_consumer_fetch_throttle_time_avg_seconds gauge\n"
+ "kafka_consumer_fetch_throttle_time_avg_seconds{client_id=\"consumer-1\"} nan 0\n"
+ "# HELP tomcat_sessions_active_max_sessions\n"
+ "# TYPE tomcat_sessions_active_max_sessions gauge\n"
+ "tomcat_sessions_active_max_sessions 0 0\n"
+ "# HELP process_cpu_usage The \"recent cpu usage\" for the Java Virtual Machine process\n"
+ "# TYPE process_cpu_usage gauge\n"
+ "process_cpu_usage 0.00070793903055696016 0\n"
+ "# HELP jvm_buffer_total_capacity_bytes An estimate of the total capacity of the buffers in this pool\n"
+ "# TYPE jvm_buffer_total_capacity_bytes gauge\n"
+ "jvm_buffer_total_capacity_bytes{id=\"mapped\"} 0 0\n"
+ "jvm_buffer_total_capacity_bytes{id=\"direct\"} 81920 0\n"
+ "# HELP kafka_consumer_fetch_throttle_time_max_seconds The maximum throttle time.\n"
+ "# TYPE kafka_consumer_fetch_throttle_time_max_seconds gauge\n"
+ "kafka_consumer_fetch_throttle_time_max_seconds{client_id=\"consumer-1\"} nan 0\n"
+ "# HELP system_load_average_1m The sum of the number of runnable entities queued to available processors and the number of runnable entities running on the available processors averaged over a period of time\n"
+ "# TYPE system_load_average_1m gauge\n"
+ "system_load_average_1m 0.52000000000000002 0\n"
+ "# HELP kafka_consumer_join_time_avg_seconds The average time taken for a group rejoin. This value can get as high as the configured session timeout for the consumer, but should usually be lower.\n"
+ "# TYPE kafka_consumer_join_time_avg_seconds gauge\n"
+ "kafka_consumer_join_time_avg_seconds{client_id=\"consumer-1\"} nan 0\n"
+ "# HELP jdbc_connections_max\n"
+ "# TYPE jdbc_connections_max gauge\n"
+ "jdbc_connections_max{name=\"dataSource\"} 10 0\n"
+ "# HELP kafka_consumer_assigned_partitions The number of partitions currently assigned to this consumer.\n"
+ "# TYPE kafka_consumer_assigned_partitions gauge\n"
+ "kafka_consumer_assigned_partitions{client_id=\"consumer-1\"} 0 0\n"
+ "# HELP kafka_consumer_heartbeat_response_time_max_seconds The max time taken to receive a response to a heartbeat request.\n"
+ "# TYPE kafka_consumer_heartbeat_response_time_max_seconds gauge\n"
+ "kafka_consumer_heartbeat_response_time_max_seconds{client_id=\"consumer-1\"} nan 0\n"
+ "# HELP jvm_threads_daemon_threads The current number of live daemon threads\n"
+ "# TYPE jvm_threads_daemon_threads gauge\n"
+ "jvm_threads_daemon_threads 20 0\n"
+ "# HELP system_cpu_count The number of processors available to the Java virtual machine\n"
+ "# TYPE system_cpu_count gauge\n"
+ "system_cpu_count 16 0\n"
+ "# HELP jvm_buffer_count_buffers An estimate of the number of buffers in the pool\n"
+ "# TYPE jvm_buffer_count_buffers gauge\n"
+ "jvm_buffer_count_buffers{id=\"mapped\"} 0 0\n"
+ "jvm_buffer_count_buffers{id=\"direct\"} 10 0\n"
+ "# HELP kafka_consumer_io_wait_time_avg_seconds The average length of time the I/O thread spent waiting for a socket to be ready for reads or writes.\n"
+ "# TYPE kafka_consumer_io_wait_time_avg_seconds gauge\n"
+ "kafka_consumer_io_wait_time_avg_seconds{client_id=\"consumer-1\"} 0.047184790159065626 0\n"
+ "# HELP jvm_memory_max_bytes The maximum amount of memory in bytes that can be used for memory management\n"
+ "# TYPE jvm_memory_max_bytes gauge\n"
+ "jvm_memory_max_bytes{area=\"nonheap\",id=\"CodeHeap 'profiled nmethods'\"} 122028032 0\n"
+ "jvm_memory_max_bytes{area=\"heap\",id=\"G1 Survivor Space\"} -1 0\n"
+ "jvm_memory_max_bytes{area=\"heap\",id=\"G1 Old Gen\"} 8331984896 0\n"
+ "jvm_memory_max_bytes{area=\"nonheap\",id=\"Metaspace\"} -1 0\n"
+ "jvm_memory_max_bytes{area=\"nonheap\",id=\"CodeHeap 'non-nmethods'\"} 7598080 0\n"
+ "jvm_memory_max_bytes{area=\"heap\",id=\"G1 Eden Space\"} -1 0\n"
+ "jvm_memory_max_bytes{area=\"nonheap\",id=\"Compressed Class Space\"} 1073741824 0\n"
+ "jvm_memory_max_bytes{area=\"nonheap\",id=\"CodeHeap 'non-profiled nmethods'\"} 122032128 0\n"
+ "# HELP jvm_gc_pause_seconds_max Time spent in GC pause\n"
+ "# TYPE jvm_gc_pause_seconds_max gauge\n"
+ "jvm_gc_pause_seconds_max{action=\"end of minor GC\",cause=\"Metadata GC Threshold\"} 0.02 0\n"
+ "jvm_gc_pause_seconds_max{action=\"end of minor GC\",cause=\"G1 Evacuation Pause\"} 0 0\n"
+ "# HELP kafka_consumer_connection_count_connections The current number of active connections.\n"
+ "# TYPE kafka_consumer_connection_count_connections gauge\n"
+ "kafka_consumer_connection_count_connections{client_id=\"consumer-1\"} 0 0\n"
+ "# HELP jdbc_connections_active\n"
+ "# TYPE jdbc_connections_active gauge\n"
+ "jdbc_connections_active{name=\"dataSource\"} 0 0\n"
+
+ "# HELP spring_kafka_listener_seconds Kafka Listener Timer\n"
+ "# TYPE spring_kafka_listener_seconds summary\n"
+ "spring_kafka_listener_seconds_sum{exception=\"ListenerExecutionFailedException\",name=\"org.springframework.kafka.KafkaListenerEndpointContainer#0-0\",result=\"failure\"} 0 0\n"
+ "spring_kafka_listener_seconds_count{exception=\"ListenerExecutionFailedException\",name=\"org.springframework.kafka.KafkaListenerEndpointContainer#0-0\",result=\"failure\"} 0 0\n"
+ "spring_kafka_listener_seconds_sum{exception=\"none\",name=\"org.springframework.kafka.KafkaListenerEndpointContainer#0-0\",result=\"success\"} 0 0\n"
+ "spring_kafka_listener_seconds_count{exception=\"none\",name=\"org.springframework.kafka.KafkaListenerEndpointContainer#0-0\",result=\"success\"} 0 0\n"
+ "# HELP hikaricp_connections_usage_seconds Connection usage time\n"
+ "# TYPE hikaricp_connections_usage_seconds summary\n"
+ "hikaricp_connections_usage_seconds_sum{pool=\"mcadb\"} 0 0\n"
+ "hikaricp_connections_usage_seconds_count{pool=\"mcadb\"} 0 0\n"
+ "# HELP jvm_gc_pause_seconds Time spent in GC pause\n"
+ "# TYPE jvm_gc_pause_seconds summary\n"
+ "jvm_gc_pause_seconds_sum{action=\"end of minor GC\",cause=\"Metadata GC Threshold\"} 0.031 0\n"
+ "jvm_gc_pause_seconds_count{action=\"end of minor GC\",cause=\"Metadata GC Threshold\"} 2 0\n"
+ "jvm_gc_pause_seconds_sum{action=\"end of minor GC\",cause=\"G1 Evacuation Pause\"} 0.016 0\n"
+ "jvm_gc_pause_seconds_count{action=\"end of minor GC\",cause=\"G1 Evacuation Pause\"} 1 0\n"
+ ;
+
+ status = cmt_decode_prometheus_create(&cmt, in_buf, in_size, &opts);
+ TEST_CHECK(status == 0);
+ if (status) {
+ fprintf(stderr, "PARSE ERROR:\n======\n%s\n======\n", errbuf);
+ }
+ else {
+ result = cmt_encode_prometheus_create(cmt, CMT_TRUE);
+ status = strcmp(result, expected);
+ TEST_CHECK(status == 0);
+ if (status) {
+ fprintf(stderr, "EXPECTED:\n======\n%s\n======\nRESULT:\n======\n%s\n======\n", expected, result);
+ }
+ }
+
+ cfl_sds_destroy(in_buf);
+ cfl_sds_destroy(result);
+ cmt_decode_prometheus_destroy(cmt);
+}
+
+void test_empty_metrics()
+{
+ int status;
+ cfl_sds_t result;
+ struct cmt *cmt;
+ struct cmt_decode_prometheus_parse_opts opts;
+ memset(&opts, 0, sizeof(opts));
+ const char in_buf[] =
+ "# HELP kube_cronjob_annotations Kubernetes annotations converted to Prometheus labels.\n"
+ "# TYPE kube_cronjob_annotations gauge\n"
+ "# HELP kube_cronjob_labels Kubernetes labels converted to Prometheus labels.\n"
+ "# TYPE kube_cronjob_labels gauge\n"
+ "# HELP kube_cronjob_info Info about cronjob.\n"
+ "# TYPE kube_cronjob_info gauge\n"
+ "# HELP kube_cronjob_created Unix creation timestamp\n"
+ "# TYPE kube_cronjob_created gauge\n"
+ "# HELP kube_cronjob_status_active Active holds pointers to currently running jobs.\n"
+ "# TYPE kube_cronjob_status_active gauge\n"
+ "# HELP kube_cronjob_status_last_schedule_time LastScheduleTime keeps information of when was the last time the job was successfully scheduled.\n"
+ "# TYPE kube_cronjob_status_last_schedule_time gauge\n"
+ "# HELP kube_cronjob_status_last_successful_time LastSuccessfulTime keeps information of when was the last time the job was completed successfully.\n"
+ "# TYPE kube_cronjob_status_last_successful_time gauge\n"
+ "# HELP kube_cronjob_spec_suspend Suspend flag tells the controller to suspend subsequent executions.\n"
+ "# TYPE kube_cronjob_spec_suspend gauge\n"
+ "# HELP kube_cronjob_spec_starting_deadline_seconds Deadline in seconds for starting the job if it misses scheduled time for any reason.\n"
+ "# TYPE kube_cronjob_spec_starting_deadline_seconds gauge\n"
+ "# HELP kube_cronjob_next_schedule_time Next time the cronjob should be scheduled. The time after lastScheduleTime, or after the cron job's creation time if it's never been scheduled. Use this to determine if the job is delayed.\n"
+ "# TYPE kube_cronjob_next_schedule_time gauge\n"
+ "# HELP kube_cronjob_metadata_resource_version Resource version representing a specific version of the cronjob.\n"
+ "# TYPE kube_cronjob_metadata_resource_version gauge\n"
+ "# HELP kube_cronjob_spec_successful_job_history_limit Successful job history limit tells the controller how many completed jobs should be preserved.\n"
+ "# TYPE kube_cronjob_spec_successful_job_history_limit gauge\n"
+ "# HELP kube_cronjob_spec_failed_job_history_limit Failed job history limit tells the controller how many failed jobs should be preserved.\n"
+ "# TYPE kube_cronjob_spec_failed_job_history_limit gauge\n"
+ ;
+
+ const char expected[] = "";
+
+ cmt_initialize();
+ status = cmt_decode_prometheus_create(&cmt, in_buf, 0, &opts);
+ TEST_CHECK(status == 0);
+ result = cmt_encode_prometheus_create(cmt, CMT_TRUE);
+ TEST_CHECK(strcmp(result, expected) == 0);
+ cfl_sds_destroy(result);
+ cmt_decode_prometheus_destroy(cmt);
+}
+
+// reproduces https://github.com/fluent/fluent-bit/issues/5894
+void test_issue_fluent_bit_6021()
+{
+ char errbuf[256];
+ int status;
+ cfl_sds_t result = NULL;
+ struct cmt *cmt;
+ struct cmt_decode_prometheus_parse_opts opts;
+ memset(&opts, 0, sizeof(opts));
+ opts.errbuf = errbuf;
+ opts.errbuf_size = sizeof(errbuf);
+ cfl_sds_t in_buf = read_file(CMT_TESTS_DATA_PATH "/issue_fluent_bit_6021.txt");
+ size_t in_size = cfl_sds_len(in_buf);
+
+ const char expected[] =
+ "# HELP envoy_cluster_manager_cds_init_fetch_timeout\n"
+ "# TYPE envoy_cluster_manager_cds_init_fetch_timeout counter\n"
+ "envoy_cluster_manager_cds_init_fetch_timeout 0 0\n"
+ "# HELP envoy_cluster_manager_cds_update_attempt\n"
+ "# TYPE envoy_cluster_manager_cds_update_attempt counter\n"
+ "envoy_cluster_manager_cds_update_attempt 1 0\n"
+ "# HELP envoy_cluster_manager_cds_update_failure\n"
+ "# TYPE envoy_cluster_manager_cds_update_failure counter\n"
+ "envoy_cluster_manager_cds_update_failure 0 0\n"
+ "# HELP envoy_http_downstream_cx_length_ms\n"
+ "# TYPE envoy_http_downstream_cx_length_ms histogram\n"
+ "envoy_http_downstream_cx_length_ms_bucket{le=\"0.5\",envoy_http_conn_manager_prefix=\"admin\"} 0 0\n"
+ "envoy_http_downstream_cx_length_ms_bucket{le=\"1.0\",envoy_http_conn_manager_prefix=\"admin\"} 0 0\n"
+ "envoy_http_downstream_cx_length_ms_bucket{le=\"5.0\",envoy_http_conn_manager_prefix=\"admin\"} 0 0\n"
+ "envoy_http_downstream_cx_length_ms_bucket{le=\"10.0\",envoy_http_conn_manager_prefix=\"admin\"} 0 0\n"
+ "envoy_http_downstream_cx_length_ms_bucket{le=\"25.0\",envoy_http_conn_manager_prefix=\"admin\"} 1 0\n"
+ "envoy_http_downstream_cx_length_ms_bucket{le=\"50.0\",envoy_http_conn_manager_prefix=\"admin\"} 1 0\n"
+ "envoy_http_downstream_cx_length_ms_bucket{le=\"100.0\",envoy_http_conn_manager_prefix=\"admin\"} 1 0\n"
+ "envoy_http_downstream_cx_length_ms_bucket{le=\"250.0\",envoy_http_conn_manager_prefix=\"admin\"} 1 0\n"
+ "envoy_http_downstream_cx_length_ms_bucket{le=\"500.0\",envoy_http_conn_manager_prefix=\"admin\"} 1 0\n"
+ "envoy_http_downstream_cx_length_ms_bucket{le=\"1000.0\",envoy_http_conn_manager_prefix=\"admin\"} 1 0\n"
+ "envoy_http_downstream_cx_length_ms_bucket{le=\"2500.0\",envoy_http_conn_manager_prefix=\"admin\"} 1 0\n"
+ "envoy_http_downstream_cx_length_ms_bucket{le=\"5000.0\",envoy_http_conn_manager_prefix=\"admin\"} 1 0\n"
+ "envoy_http_downstream_cx_length_ms_bucket{le=\"10000.0\",envoy_http_conn_manager_prefix=\"admin\"} 1 0\n"
+ "envoy_http_downstream_cx_length_ms_bucket{le=\"30000.0\",envoy_http_conn_manager_prefix=\"admin\"} 1 0\n"
+ "envoy_http_downstream_cx_length_ms_bucket{le=\"60000.0\",envoy_http_conn_manager_prefix=\"admin\"} 1 0\n"
+ "envoy_http_downstream_cx_length_ms_bucket{le=\"300000.0\",envoy_http_conn_manager_prefix=\"admin\"} 1 0\n"
+ "envoy_http_downstream_cx_length_ms_bucket{le=\"600000.0\",envoy_http_conn_manager_prefix=\"admin\"} 1 0\n"
+ "envoy_http_downstream_cx_length_ms_bucket{le=\"1.8e+06\",envoy_http_conn_manager_prefix=\"admin\"} 1 0\n"
+ "envoy_http_downstream_cx_length_ms_bucket{le=\"3.6e+06\",envoy_http_conn_manager_prefix=\"admin\"} 1 0\n"
+ "envoy_http_downstream_cx_length_ms_bucket{le=\"+Inf\",envoy_http_conn_manager_prefix=\"admin\"} 1 0\n"
+ "envoy_http_downstream_cx_length_ms_sum{envoy_http_conn_manager_prefix=\"admin\"} 15.5 0\n"
+ "envoy_http_downstream_cx_length_ms_count{envoy_http_conn_manager_prefix=\"admin\"} 1 0\n"
+ "envoy_http_downstream_cx_length_ms_bucket{le=\"0.5\",envoy_http_conn_manager_prefix=\"ingress_http\"} 0 0\n"
+ "envoy_http_downstream_cx_length_ms_bucket{le=\"1.0\",envoy_http_conn_manager_prefix=\"ingress_http\"} 0 0\n"
+ "envoy_http_downstream_cx_length_ms_bucket{le=\"5.0\",envoy_http_conn_manager_prefix=\"ingress_http\"} 0 0\n"
+ "envoy_http_downstream_cx_length_ms_bucket{le=\"10.0\",envoy_http_conn_manager_prefix=\"ingress_http\"} 0 0\n"
+ "envoy_http_downstream_cx_length_ms_bucket{le=\"25.0\",envoy_http_conn_manager_prefix=\"ingress_http\"} 0 0\n"
+ "envoy_http_downstream_cx_length_ms_bucket{le=\"50.0\",envoy_http_conn_manager_prefix=\"ingress_http\"} 0 0\n"
+ "envoy_http_downstream_cx_length_ms_bucket{le=\"100.0\",envoy_http_conn_manager_prefix=\"ingress_http\"} 0 0\n"
+ "envoy_http_downstream_cx_length_ms_bucket{le=\"250.0\",envoy_http_conn_manager_prefix=\"ingress_http\"} 0 0\n"
+ "envoy_http_downstream_cx_length_ms_bucket{le=\"500.0\",envoy_http_conn_manager_prefix=\"ingress_http\"} 0 0\n"
+ "envoy_http_downstream_cx_length_ms_bucket{le=\"1000.0\",envoy_http_conn_manager_prefix=\"ingress_http\"} 0 0\n"
+ "envoy_http_downstream_cx_length_ms_bucket{le=\"2500.0\",envoy_http_conn_manager_prefix=\"ingress_http\"} 0 0\n"
+ "envoy_http_downstream_cx_length_ms_bucket{le=\"5000.0\",envoy_http_conn_manager_prefix=\"ingress_http\"} 0 0\n"
+ "envoy_http_downstream_cx_length_ms_bucket{le=\"10000.0\",envoy_http_conn_manager_prefix=\"ingress_http\"} 0 0\n"
+ "envoy_http_downstream_cx_length_ms_bucket{le=\"30000.0\",envoy_http_conn_manager_prefix=\"ingress_http\"} 0 0\n"
+ "envoy_http_downstream_cx_length_ms_bucket{le=\"60000.0\",envoy_http_conn_manager_prefix=\"ingress_http\"} 0 0\n"
+ "envoy_http_downstream_cx_length_ms_bucket{le=\"300000.0\",envoy_http_conn_manager_prefix=\"ingress_http\"} 0 0\n"
+ "envoy_http_downstream_cx_length_ms_bucket{le=\"600000.0\",envoy_http_conn_manager_prefix=\"ingress_http\"} 0 0\n"
+ "envoy_http_downstream_cx_length_ms_bucket{le=\"1.8e+06\",envoy_http_conn_manager_prefix=\"ingress_http\"} 0 0\n"
+ "envoy_http_downstream_cx_length_ms_bucket{le=\"3.6e+06\",envoy_http_conn_manager_prefix=\"ingress_http\"} 0 0\n"
+ "envoy_http_downstream_cx_length_ms_bucket{le=\"+Inf\",envoy_http_conn_manager_prefix=\"ingress_http\"} 0 0\n"
+ "envoy_http_downstream_cx_length_ms_sum{envoy_http_conn_manager_prefix=\"ingress_http\"} 0 0\n"
+ "envoy_http_downstream_cx_length_ms_count{envoy_http_conn_manager_prefix=\"ingress_http\"} 0 0\n"
+ "# HELP envoy_http_downstream_rq_time\n"
+ "# TYPE envoy_http_downstream_rq_time histogram\n"
+ "envoy_http_downstream_rq_time_bucket{le=\"0.5\",envoy_http_conn_manager_prefix=\"admin\"} 0 0\n"
+ "envoy_http_downstream_rq_time_bucket{le=\"1.0\",envoy_http_conn_manager_prefix=\"admin\"} 0 0\n"
+ "envoy_http_downstream_rq_time_bucket{le=\"5.0\",envoy_http_conn_manager_prefix=\"admin\"} 10 0\n"
+ "envoy_http_downstream_rq_time_bucket{le=\"10.0\",envoy_http_conn_manager_prefix=\"admin\"} 10 0\n"
+ "envoy_http_downstream_rq_time_bucket{le=\"25.0\",envoy_http_conn_manager_prefix=\"admin\"} 10 0\n"
+ "envoy_http_downstream_rq_time_bucket{le=\"50.0\",envoy_http_conn_manager_prefix=\"admin\"} 10 0\n"
+ "envoy_http_downstream_rq_time_bucket{le=\"100.0\",envoy_http_conn_manager_prefix=\"admin\"} 10 0\n"
+ "envoy_http_downstream_rq_time_bucket{le=\"250.0\",envoy_http_conn_manager_prefix=\"admin\"} 10 0\n"
+ "envoy_http_downstream_rq_time_bucket{le=\"500.0\",envoy_http_conn_manager_prefix=\"admin\"} 10 0\n"
+ "envoy_http_downstream_rq_time_bucket{le=\"1000.0\",envoy_http_conn_manager_prefix=\"admin\"} 10 0\n"
+ "envoy_http_downstream_rq_time_bucket{le=\"2500.0\",envoy_http_conn_manager_prefix=\"admin\"} 10 0\n"
+ "envoy_http_downstream_rq_time_bucket{le=\"5000.0\",envoy_http_conn_manager_prefix=\"admin\"} 10 0\n"
+ "envoy_http_downstream_rq_time_bucket{le=\"10000.0\",envoy_http_conn_manager_prefix=\"admin\"} 10 0\n"
+ "envoy_http_downstream_rq_time_bucket{le=\"30000.0\",envoy_http_conn_manager_prefix=\"admin\"} 10 0\n"
+ "envoy_http_downstream_rq_time_bucket{le=\"60000.0\",envoy_http_conn_manager_prefix=\"admin\"} 10 0\n"
+ "envoy_http_downstream_rq_time_bucket{le=\"300000.0\",envoy_http_conn_manager_prefix=\"admin\"} 10 0\n"
+ "envoy_http_downstream_rq_time_bucket{le=\"600000.0\",envoy_http_conn_manager_prefix=\"admin\"} 10 0\n"
+ "envoy_http_downstream_rq_time_bucket{le=\"1.8e+06\",envoy_http_conn_manager_prefix=\"admin\"} 10 0\n"
+ "envoy_http_downstream_rq_time_bucket{le=\"3.6e+06\",envoy_http_conn_manager_prefix=\"admin\"} 10 0\n"
+ "envoy_http_downstream_rq_time_bucket{le=\"+Inf\",envoy_http_conn_manager_prefix=\"admin\"} 10 0\n"
+ "envoy_http_downstream_rq_time_sum{envoy_http_conn_manager_prefix=\"admin\"} 25.5 0\n"
+ "envoy_http_downstream_rq_time_count{envoy_http_conn_manager_prefix=\"admin\"} 10 0\n"
+ "envoy_http_downstream_rq_time_bucket{le=\"0.5\",envoy_http_conn_manager_prefix=\"ingress_http\"} 0 0\n"
+ "envoy_http_downstream_rq_time_bucket{le=\"1.0\",envoy_http_conn_manager_prefix=\"ingress_http\"} 0 0\n"
+ "envoy_http_downstream_rq_time_bucket{le=\"5.0\",envoy_http_conn_manager_prefix=\"ingress_http\"} 0 0\n"
+ "envoy_http_downstream_rq_time_bucket{le=\"10.0\",envoy_http_conn_manager_prefix=\"ingress_http\"} 0 0\n"
+ "envoy_http_downstream_rq_time_bucket{le=\"25.0\",envoy_http_conn_manager_prefix=\"ingress_http\"} 0 0\n"
+ "envoy_http_downstream_rq_time_bucket{le=\"50.0\",envoy_http_conn_manager_prefix=\"ingress_http\"} 0 0\n"
+ "envoy_http_downstream_rq_time_bucket{le=\"100.0\",envoy_http_conn_manager_prefix=\"ingress_http\"} 0 0\n"
+ "envoy_http_downstream_rq_time_bucket{le=\"250.0\",envoy_http_conn_manager_prefix=\"ingress_http\"} 0 0\n"
+ "envoy_http_downstream_rq_time_bucket{le=\"500.0\",envoy_http_conn_manager_prefix=\"ingress_http\"} 0 0\n"
+ "envoy_http_downstream_rq_time_bucket{le=\"1000.0\",envoy_http_conn_manager_prefix=\"ingress_http\"} 0 0\n"
+ "envoy_http_downstream_rq_time_bucket{le=\"2500.0\",envoy_http_conn_manager_prefix=\"ingress_http\"} 0 0\n"
+ "envoy_http_downstream_rq_time_bucket{le=\"5000.0\",envoy_http_conn_manager_prefix=\"ingress_http\"} 0 0\n"
+ "envoy_http_downstream_rq_time_bucket{le=\"10000.0\",envoy_http_conn_manager_prefix=\"ingress_http\"} 0 0\n"
+ "envoy_http_downstream_rq_time_bucket{le=\"30000.0\",envoy_http_conn_manager_prefix=\"ingress_http\"} 0 0\n"
+ "envoy_http_downstream_rq_time_bucket{le=\"60000.0\",envoy_http_conn_manager_prefix=\"ingress_http\"} 0 0\n"
+ "envoy_http_downstream_rq_time_bucket{le=\"300000.0\",envoy_http_conn_manager_prefix=\"ingress_http\"} 0 0\n"
+ "envoy_http_downstream_rq_time_bucket{le=\"600000.0\",envoy_http_conn_manager_prefix=\"ingress_http\"} 0 0\n"
+ "envoy_http_downstream_rq_time_bucket{le=\"1.8e+06\",envoy_http_conn_manager_prefix=\"ingress_http\"} 0 0\n"
+ "envoy_http_downstream_rq_time_bucket{le=\"3.6e+06\",envoy_http_conn_manager_prefix=\"ingress_http\"} 0 0\n"
+ "envoy_http_downstream_rq_time_bucket{le=\"+Inf\",envoy_http_conn_manager_prefix=\"ingress_http\"} 0 0\n"
+ "envoy_http_downstream_rq_time_sum{envoy_http_conn_manager_prefix=\"ingress_http\"} 0 0\n"
+ "envoy_http_downstream_rq_time_count{envoy_http_conn_manager_prefix=\"ingress_http\"} 0 0\n"
+ "# HELP envoy_listener_admin_downstream_cx_length_ms\n"
+ "# TYPE envoy_listener_admin_downstream_cx_length_ms histogram\n"
+ "envoy_listener_admin_downstream_cx_length_ms_bucket{le=\"0.5\"} 0 0\n"
+ "envoy_listener_admin_downstream_cx_length_ms_bucket{le=\"1.0\"} 0 0\n"
+ "envoy_listener_admin_downstream_cx_length_ms_bucket{le=\"5.0\"} 0 0\n"
+ "envoy_listener_admin_downstream_cx_length_ms_bucket{le=\"10.0\"} 0 0\n"
+ "envoy_listener_admin_downstream_cx_length_ms_bucket{le=\"25.0\"} 1 0\n"
+ "envoy_listener_admin_downstream_cx_length_ms_bucket{le=\"50.0\"} 1 0\n"
+ "envoy_listener_admin_downstream_cx_length_ms_bucket{le=\"100.0\"} 1 0\n"
+ "envoy_listener_admin_downstream_cx_length_ms_bucket{le=\"250.0\"} 1 0\n"
+ "envoy_listener_admin_downstream_cx_length_ms_bucket{le=\"500.0\"} 1 0\n"
+ "envoy_listener_admin_downstream_cx_length_ms_bucket{le=\"1000.0\"} 1 0\n"
+ "envoy_listener_admin_downstream_cx_length_ms_bucket{le=\"2500.0\"} 1 0\n"
+ "envoy_listener_admin_downstream_cx_length_ms_bucket{le=\"5000.0\"} 1 0\n"
+ "envoy_listener_admin_downstream_cx_length_ms_bucket{le=\"10000.0\"} 1 0\n"
+ "envoy_listener_admin_downstream_cx_length_ms_bucket{le=\"30000.0\"} 1 0\n"
+ "envoy_listener_admin_downstream_cx_length_ms_bucket{le=\"60000.0\"} 1 0\n"
+ "envoy_listener_admin_downstream_cx_length_ms_bucket{le=\"300000.0\"} 1 0\n"
+ "envoy_listener_admin_downstream_cx_length_ms_bucket{le=\"600000.0\"} 1 0\n"
+ "envoy_listener_admin_downstream_cx_length_ms_bucket{le=\"1.8e+06\"} 1 0\n"
+ "envoy_listener_admin_downstream_cx_length_ms_bucket{le=\"3.6e+06\"} 1 0\n"
+ "envoy_listener_admin_downstream_cx_length_ms_bucket{le=\"+Inf\"} 1 0\n"
+ "envoy_listener_admin_downstream_cx_length_ms_sum 15.5 0\n"
+ "envoy_listener_admin_downstream_cx_length_ms_count 1 0\n"
+ "# HELP envoy_listener_downstream_cx_length_ms\n"
+ "# TYPE envoy_listener_downstream_cx_length_ms histogram\n"
+ "envoy_listener_downstream_cx_length_ms_bucket{le=\"0.5\",envoy_listener_address=\"0.0.0.0_10000\"} 0 0\n"
+ "envoy_listener_downstream_cx_length_ms_bucket{le=\"1.0\",envoy_listener_address=\"0.0.0.0_10000\"} 0 0\n"
+ "envoy_listener_downstream_cx_length_ms_bucket{le=\"5.0\",envoy_listener_address=\"0.0.0.0_10000\"} 0 0\n"
+ "envoy_listener_downstream_cx_length_ms_bucket{le=\"10.0\",envoy_listener_address=\"0.0.0.0_10000\"} 0 0\n"
+ "envoy_listener_downstream_cx_length_ms_bucket{le=\"25.0\",envoy_listener_address=\"0.0.0.0_10000\"} 0 0\n"
+ "envoy_listener_downstream_cx_length_ms_bucket{le=\"50.0\",envoy_listener_address=\"0.0.0.0_10000\"} 0 0\n"
+ "envoy_listener_downstream_cx_length_ms_bucket{le=\"100.0\",envoy_listener_address=\"0.0.0.0_10000\"} 0 0\n"
+ "envoy_listener_downstream_cx_length_ms_bucket{le=\"250.0\",envoy_listener_address=\"0.0.0.0_10000\"} 0 0\n"
+ "envoy_listener_downstream_cx_length_ms_bucket{le=\"500.0\",envoy_listener_address=\"0.0.0.0_10000\"} 0 0\n"
+ "envoy_listener_downstream_cx_length_ms_bucket{le=\"1000.0\",envoy_listener_address=\"0.0.0.0_10000\"} 0 0\n"
+ "envoy_listener_downstream_cx_length_ms_bucket{le=\"2500.0\",envoy_listener_address=\"0.0.0.0_10000\"} 0 0\n"
+ "envoy_listener_downstream_cx_length_ms_bucket{le=\"5000.0\",envoy_listener_address=\"0.0.0.0_10000\"} 0 0\n"
+ "envoy_listener_downstream_cx_length_ms_bucket{le=\"10000.0\",envoy_listener_address=\"0.0.0.0_10000\"} 0 0\n"
+ "envoy_listener_downstream_cx_length_ms_bucket{le=\"30000.0\",envoy_listener_address=\"0.0.0.0_10000\"} 0 0\n"
+ "envoy_listener_downstream_cx_length_ms_bucket{le=\"60000.0\",envoy_listener_address=\"0.0.0.0_10000\"} 0 0\n"
+ "envoy_listener_downstream_cx_length_ms_bucket{le=\"300000.0\",envoy_listener_address=\"0.0.0.0_10000\"} 0 0\n"
+ "envoy_listener_downstream_cx_length_ms_bucket{le=\"600000.0\",envoy_listener_address=\"0.0.0.0_10000\"} 0 0\n"
+ "envoy_listener_downstream_cx_length_ms_bucket{le=\"1.8e+06\",envoy_listener_address=\"0.0.0.0_10000\"} 0 0\n"
+ "envoy_listener_downstream_cx_length_ms_bucket{le=\"3.6e+06\",envoy_listener_address=\"0.0.0.0_10000\"} 0 0\n"
+ "envoy_listener_downstream_cx_length_ms_bucket{le=\"+Inf\",envoy_listener_address=\"0.0.0.0_10000\"} 0 0\n"
+ "envoy_listener_downstream_cx_length_ms_sum{envoy_listener_address=\"0.0.0.0_10000\"} 0 0\n"
+ "envoy_listener_downstream_cx_length_ms_count{envoy_listener_address=\"0.0.0.0_10000\"} 0 0\n"
+ "# HELP envoy_listener_manager_lds_update_duration\n"
+ "# TYPE envoy_listener_manager_lds_update_duration histogram\n"
+ "envoy_listener_manager_lds_update_duration_bucket{le=\"0.5\"} 0 0\n"
+ "envoy_listener_manager_lds_update_duration_bucket{le=\"1.0\"} 0 0\n"
+ "envoy_listener_manager_lds_update_duration_bucket{le=\"5.0\"} 0 0\n"
+ "envoy_listener_manager_lds_update_duration_bucket{le=\"10.0\"} 0 0\n"
+ "envoy_listener_manager_lds_update_duration_bucket{le=\"25.0\"} 0 0\n"
+ "envoy_listener_manager_lds_update_duration_bucket{le=\"50.0\"} 0 0\n"
+ "envoy_listener_manager_lds_update_duration_bucket{le=\"100.0\"} 0 0\n"
+ "envoy_listener_manager_lds_update_duration_bucket{le=\"250.0\"} 0 0\n"
+ "envoy_listener_manager_lds_update_duration_bucket{le=\"500.0\"} 0 0\n"
+ "envoy_listener_manager_lds_update_duration_bucket{le=\"1000.0\"} 0 0\n"
+ "envoy_listener_manager_lds_update_duration_bucket{le=\"2500.0\"} 0 0\n"
+ "envoy_listener_manager_lds_update_duration_bucket{le=\"5000.0\"} 0 0\n"
+ "envoy_listener_manager_lds_update_duration_bucket{le=\"10000.0\"} 0 0\n"
+ "envoy_listener_manager_lds_update_duration_bucket{le=\"30000.0\"} 0 0\n"
+ "envoy_listener_manager_lds_update_duration_bucket{le=\"60000.0\"} 0 0\n"
+ "envoy_listener_manager_lds_update_duration_bucket{le=\"300000.0\"} 0 0\n"
+ "envoy_listener_manager_lds_update_duration_bucket{le=\"600000.0\"} 0 0\n"
+ "envoy_listener_manager_lds_update_duration_bucket{le=\"1.8e+06\"} 0 0\n"
+ "envoy_listener_manager_lds_update_duration_bucket{le=\"3.6e+06\"} 0 0\n"
+ "envoy_listener_manager_lds_update_duration_bucket{le=\"+Inf\"} 0 0\n"
+ "envoy_listener_manager_lds_update_duration_sum 0 0\n"
+ "envoy_listener_manager_lds_update_duration_count 0 0\n"
+ "# HELP envoy_sds_tls_sds_update_duration\n"
+ "# TYPE envoy_sds_tls_sds_update_duration histogram\n"
+ "envoy_sds_tls_sds_update_duration_bucket{le=\"0.5\"} 0 0\n"
+ "envoy_sds_tls_sds_update_duration_bucket{le=\"1.0\"} 0 0\n"
+ "envoy_sds_tls_sds_update_duration_bucket{le=\"5.0\"} 0 0\n"
+ "envoy_sds_tls_sds_update_duration_bucket{le=\"10.0\"} 0 0\n"
+ "envoy_sds_tls_sds_update_duration_bucket{le=\"25.0\"} 0 0\n"
+ "envoy_sds_tls_sds_update_duration_bucket{le=\"50.0\"} 0 0\n"
+ "envoy_sds_tls_sds_update_duration_bucket{le=\"100.0\"} 0 0\n"
+ "envoy_sds_tls_sds_update_duration_bucket{le=\"250.0\"} 0 0\n"
+ "envoy_sds_tls_sds_update_duration_bucket{le=\"500.0\"} 0 0\n"
+ "envoy_sds_tls_sds_update_duration_bucket{le=\"1000.0\"} 0 0\n"
+ "envoy_sds_tls_sds_update_duration_bucket{le=\"2500.0\"} 0 0\n"
+ "envoy_sds_tls_sds_update_duration_bucket{le=\"5000.0\"} 0 0\n"
+ "envoy_sds_tls_sds_update_duration_bucket{le=\"10000.0\"} 0 0\n"
+ "envoy_sds_tls_sds_update_duration_bucket{le=\"30000.0\"} 0 0\n"
+ "envoy_sds_tls_sds_update_duration_bucket{le=\"60000.0\"} 0 0\n"
+ "envoy_sds_tls_sds_update_duration_bucket{le=\"300000.0\"} 0 0\n"
+ "envoy_sds_tls_sds_update_duration_bucket{le=\"600000.0\"} 0 0\n"
+ "envoy_sds_tls_sds_update_duration_bucket{le=\"1.8e+06\"} 0 0\n"
+ "envoy_sds_tls_sds_update_duration_bucket{le=\"3.6e+06\"} 0 0\n"
+ "envoy_sds_tls_sds_update_duration_bucket{le=\"+Inf\"} 0 0\n"
+ "envoy_sds_tls_sds_update_duration_sum 0 0\n"
+ "envoy_sds_tls_sds_update_duration_count 0 0\n"
+ "# HELP envoy_server_initialization_time_ms\n"
+ "# TYPE envoy_server_initialization_time_ms histogram\n"
+ "envoy_server_initialization_time_ms_bucket{le=\"0.5\"} 0 0\n"
+ "envoy_server_initialization_time_ms_bucket{le=\"1.0\"} 0 0\n"
+ "envoy_server_initialization_time_ms_bucket{le=\"5.0\"} 0 0\n"
+ "envoy_server_initialization_time_ms_bucket{le=\"10.0\"} 0 0\n"
+ "envoy_server_initialization_time_ms_bucket{le=\"25.0\"} 0 0\n"
+ "envoy_server_initialization_time_ms_bucket{le=\"50.0\"} 1 0\n"
+ "envoy_server_initialization_time_ms_bucket{le=\"100.0\"} 1 0\n"
+ "envoy_server_initialization_time_ms_bucket{le=\"250.0\"} 1 0\n"
+ "envoy_server_initialization_time_ms_bucket{le=\"500.0\"} 1 0\n"
+ "envoy_server_initialization_time_ms_bucket{le=\"1000.0\"} 1 0\n"
+ "envoy_server_initialization_time_ms_bucket{le=\"2500.0\"} 1 0\n"
+ "envoy_server_initialization_time_ms_bucket{le=\"5000.0\"} 1 0\n"
+ "envoy_server_initialization_time_ms_bucket{le=\"10000.0\"} 1 0\n"
+ "envoy_server_initialization_time_ms_bucket{le=\"30000.0\"} 1 0\n"
+ "envoy_server_initialization_time_ms_bucket{le=\"60000.0\"} 1 0\n"
+ "envoy_server_initialization_time_ms_bucket{le=\"300000.0\"} 1 0\n"
+ "envoy_server_initialization_time_ms_bucket{le=\"600000.0\"} 1 0\n"
+ "envoy_server_initialization_time_ms_bucket{le=\"1.8e+06\"} 1 0\n"
+ "envoy_server_initialization_time_ms_bucket{le=\"3.6e+06\"} 1 0\n"
+ "envoy_server_initialization_time_ms_bucket{le=\"+Inf\"} 1 0\n"
+ "envoy_server_initialization_time_ms_sum 30.5 0\n"
+ "envoy_server_initialization_time_ms_count 1 0\n"
+ ;
+
+ status = cmt_decode_prometheus_create(&cmt, in_buf, in_size, &opts);
+ TEST_CHECK(status == 0);
+ if (status) {
+ fprintf(stderr, "PARSE ERROR:\n======\n%s\n======\n", errbuf);
+ }
+ else {
+ result = cmt_encode_prometheus_create(cmt, CMT_TRUE);
+ status = strcmp(result, expected);
+ TEST_CHECK(status == 0);
+ if (status) {
+ fprintf(stderr, "EXPECTED:\n======\n%s\n======\nRESULT:\n======\n%s\n======\n", expected, result);
+ }
+ }
+
+ cfl_sds_destroy(in_buf);
+ cfl_sds_destroy(result);
+ cmt_decode_prometheus_destroy(cmt);
+}
+
+void test_override_timestamp()
+{
+ int status;
+ cfl_sds_t result = NULL;
+ struct cmt *cmt;
+ struct cmt_decode_prometheus_parse_opts opts;
+ memset(&opts, 0, sizeof(opts));
+ opts.override_timestamp = 123;
+ const char in_buf[] =
+ "# HELP hikaricp_connections_timeout_total Connection timeout total count\n"
+ "# TYPE hikaricp_connections_timeout_total counter\n"
+ "hikaricp_connections_timeout_total{pool=\"mcadb\"} 0 0\n"
+ "# HELP rabbitmq_consumed_total\n"
+ "# TYPE rabbitmq_consumed_total counter\n"
+ "rabbitmq_consumed_total{name=\"rabbit\"} 0 0\n"
+ "# HELP rabbitmq_failed_to_publish_total\n"
+ "# TYPE rabbitmq_failed_to_publish_total counter\n"
+ "rabbitmq_failed_to_publish_total{name=\"rabbit\"} 0 0\n"
+ "# HELP rabbitmq_acknowledged_published_total\n"
+ "# TYPE rabbitmq_acknowledged_published_total counter\n"
+ "rabbitmq_acknowledged_published_total{name=\"rabbit\"} 0 0\n"
+ "# HELP tomcat_sessions_rejected_sessions_total\n"
+ "# TYPE tomcat_sessions_rejected_sessions_total counter\n"
+ "tomcat_sessions_rejected_sessions_total 0 0\n"
+ "# A histogram, which has a pretty complex representation in the text format:\n"
+ "# HELP http_request_duration_seconds_bucket A histogram of the request duration.\n"
+ "# TYPE http_request_duration_seconds_bucket counter\n"
+ "http_request_duration_seconds_bucket{le=\"0.05\"} 24054\n"
+ "http_request_duration_seconds_bucket{le=\"0.1\"} 33444\n"
+ "http_request_duration_seconds_bucket{le=\"0.2\"} 100392\n"
+ "http_request_duration_seconds_bucket{le=\"0.5\"} 129389\n"
+ "http_request_duration_seconds_bucket{le=\"1\"} 133988\n"
+ "http_request_duration_seconds_bucket{le=\"+Inf\"} 144320\n"
+ "http_request_duration_seconds_sum 53423\n"
+ "http_request_duration_seconds_count 144320\n"
+ ;
+
+ const char expected[] =
+ "# HELP hikaricp_connections_timeout_total Connection timeout total count\n"
+ "# TYPE hikaricp_connections_timeout_total counter\n"
+ "hikaricp_connections_timeout_total{pool=\"mcadb\"} 0 123\n"
+ "# HELP http_request_duration_seconds_bucket A histogram of the request duration.\n"
+ "# TYPE http_request_duration_seconds_bucket counter\n"
+ "http_request_duration_seconds_bucket{le=\"0.05\"} 24054 123\n"
+ "http_request_duration_seconds_bucket{le=\"0.1\"} 33444 123\n"
+ "http_request_duration_seconds_bucket{le=\"0.2\"} 100392 123\n"
+ "http_request_duration_seconds_bucket{le=\"0.5\"} 129389 123\n"
+ "http_request_duration_seconds_bucket{le=\"1\"} 133988 123\n"
+ "http_request_duration_seconds_bucket{le=\"+Inf\"} 144320 123\n"
+ "# HELP rabbitmq_consumed_total\n"
+ "# TYPE rabbitmq_consumed_total untyped\n"
+ "rabbitmq_consumed_total{name=\"rabbit\"} 0 123\n"
+ "# HELP rabbitmq_failed_to_publish_total\n"
+ "# TYPE rabbitmq_failed_to_publish_total untyped\n"
+ "rabbitmq_failed_to_publish_total{name=\"rabbit\"} 0 123\n"
+ "# HELP rabbitmq_acknowledged_published_total\n"
+ "# TYPE rabbitmq_acknowledged_published_total untyped\n"
+ "rabbitmq_acknowledged_published_total{name=\"rabbit\"} 0 123\n"
+ "# HELP tomcat_sessions_rejected_sessions_total\n"
+ "# TYPE tomcat_sessions_rejected_sessions_total untyped\n"
+ "tomcat_sessions_rejected_sessions_total 0 123\n"
+ "# HELP http_request_duration_seconds_sum\n"
+ "# TYPE http_request_duration_seconds_sum untyped\n"
+ "http_request_duration_seconds_sum 53423 123\n"
+ "# HELP http_request_duration_seconds_count\n"
+ "# TYPE http_request_duration_seconds_count untyped\n"
+ "http_request_duration_seconds_count 144320 123\n"
+ ;
+
+ cmt_initialize();
+ status = cmt_decode_prometheus_create(&cmt, in_buf, 0, &opts);
+ TEST_CHECK(status == 0);
+ if (!status) {
+ result = cmt_encode_prometheus_create(cmt, CMT_TRUE);
+ status = strcmp(result, expected);
+ TEST_CHECK(status == 0);
+ if (status) {
+ fprintf(stderr, "EXPECTED:\n======\n%s\n======\nRESULT:\n======\n%s\n======\n", expected, result);
+ }
+ }
+ cfl_sds_destroy(result);
+ cmt_decode_prometheus_destroy(cmt);
+}
+
+// testing bug uncovered by https://github.com/fluent/cmetrics/pull/168
+void test_pr_168()
+{
+ char errbuf[256];
+ int status;
+ struct cmt *cmt;
+ cfl_sds_t result = NULL;
+ struct cmt_decode_prometheus_parse_opts opts;
+ memset(&opts, 0, sizeof(opts));
+ opts.errbuf = errbuf;
+ opts.errbuf_size = sizeof(errbuf);
+ cfl_sds_t in_buf = read_file(CMT_TESTS_DATA_PATH "/pr_168.txt");
+ const char expected[] =
+ "# HELP prometheus_engine_query_duration_seconds Query timings\n"
+ "# TYPE prometheus_engine_query_duration_seconds summary\n"
+ "prometheus_engine_query_duration_seconds{quantile=\"0.5\",slice=\"inner_eval\"} nan 0\n"
+ "prometheus_engine_query_duration_seconds{quantile=\"0.9\",slice=\"inner_eval\"} nan 0\n"
+ "prometheus_engine_query_duration_seconds{quantile=\"0.99\",slice=\"inner_eval\"} nan 0\n"
+ "prometheus_engine_query_duration_seconds_sum{slice=\"inner_eval\"} 0 0\n"
+ "prometheus_engine_query_duration_seconds_count{slice=\"inner_eval\"} 0 0\n"
+ "prometheus_engine_query_duration_seconds{quantile=\"0.5\",slice=\"prepare_time\"} 0 0\n"
+ "prometheus_engine_query_duration_seconds{quantile=\"0.9\",slice=\"prepare_time\"} 0 0\n"
+ "prometheus_engine_query_duration_seconds{quantile=\"0.99\",slice=\"prepare_time\"} 0 0\n"
+ "prometheus_engine_query_duration_seconds_sum{slice=\"prepare_time\"} 0 0\n"
+ "prometheus_engine_query_duration_seconds_count{slice=\"prepare_time\"} 0 0\n"
+ "prometheus_engine_query_duration_seconds{quantile=\"0.5\",slice=\"queue_time\"} 0 0\n"
+ "prometheus_engine_query_duration_seconds{quantile=\"0.9\",slice=\"queue_time\"} 0 0\n"
+ "prometheus_engine_query_duration_seconds{quantile=\"0.99\",slice=\"queue_time\"} 0 0\n"
+ "prometheus_engine_query_duration_seconds_sum{slice=\"queue_time\"} 0 0\n"
+ "prometheus_engine_query_duration_seconds_count{slice=\"queue_time\"} 0 0\n"
+ "prometheus_engine_query_duration_seconds{quantile=\"0.5\",slice=\"result_sort\"} 0 0\n"
+ "prometheus_engine_query_duration_seconds{quantile=\"0.9\",slice=\"result_sort\"} 0 0\n"
+ "prometheus_engine_query_duration_seconds{quantile=\"0.99\",slice=\"result_sort\"} 0 0\n"
+ "prometheus_engine_query_duration_seconds_sum{slice=\"result_sort\"} 0 0\n"
+ "prometheus_engine_query_duration_seconds_count{slice=\"result_sort\"} 0 0\n";
+
+ cmt_initialize();
+ status = cmt_decode_prometheus_create(&cmt, in_buf, cfl_sds_len(in_buf), &opts);
+ TEST_CHECK(status == 0);
+ if (!status) {
+ result = cmt_encode_prometheus_create(cmt, CMT_TRUE);
+ status = strcmp(result, expected);
+ TEST_CHECK(status == 0);
+ if (status) {
+ fprintf(stderr, "EXPECTED:\n======\n%s\n======\nRESULT:\n======\n%s\n======\n", expected, result);
+ }
+ cfl_sds_destroy(result);
+ }
+ cfl_sds_destroy(in_buf);
+ cmt_decode_prometheus_destroy(cmt);
+}
+
+void test_histogram_different_label_count()
+{
+ char errbuf[256];
+ int status;
+ struct cmt *cmt;
+ cfl_sds_t result = NULL;
+ struct cmt_decode_prometheus_parse_opts opts;
+ memset(&opts, 0, sizeof(opts));
+ opts.errbuf = errbuf;
+ opts.errbuf_size = sizeof(errbuf);
+ cfl_sds_t in_buf = read_file(CMT_TESTS_DATA_PATH "/histogram_different_label_count.txt");
+ const char expected[] =
+ "# HELP k8s_network_load Network load\n"
+ "# TYPE k8s_network_load histogram\n"
+ "k8s_network_load_bucket{le=\"0.05\"} 0 0\n"
+ "k8s_network_load_bucket{le=\"5.0\"} 1 0\n"
+ "k8s_network_load_bucket{le=\"10.0\"} 2 0\n"
+ "k8s_network_load_bucket{le=\"+Inf\"} 3 0\n"
+ "k8s_network_load_sum 1013 0\n"
+ "k8s_network_load_count 3 0\n"
+ "# HELP k8s_network_load Network load\n"
+ "# TYPE k8s_network_load histogram\n"
+ "k8s_network_load_bucket{le=\"0.05\",my_label=\"my_val\"} 0 0\n"
+ "k8s_network_load_bucket{le=\"5.0\",my_label=\"my_val\"} 1 0\n"
+ "k8s_network_load_bucket{le=\"10.0\",my_label=\"my_val\"} 2 0\n"
+ "k8s_network_load_bucket{le=\"+Inf\",my_label=\"my_val\"} 3 0\n"
+ "k8s_network_load_sum{my_label=\"my_val\"} 1013 0\n"
+ "k8s_network_load_count{my_label=\"my_val\"} 3 0\n"
+ ;
+
+ cmt_initialize();
+ status = cmt_decode_prometheus_create(&cmt, in_buf, cfl_sds_len(in_buf), &opts);
+ TEST_CHECK(status == 0);
+ if (!status) {
+ result = cmt_encode_prometheus_create(cmt, CMT_TRUE);
+ status = strcmp(result, expected);
+ TEST_CHECK(status == 0);
+ if (status) {
+ fprintf(stderr, "EXPECTED:\n======\n%s\n======\nRESULT:\n======\n%s\n======\n", expected, result);
+ }
+ cfl_sds_destroy(result);
+ }
+ cfl_sds_destroy(in_buf);
+ cmt_decode_prometheus_destroy(cmt);
+}
+
+// reproduces https://github.com/fluent/fluent-bit/issues/6534
+void test_issue_fluent_bit_6534()
+{
+ char errbuf[256];
+ int status;
+ cfl_sds_t result = NULL;
+ struct cmt *cmt;
+ struct cmt_decode_prometheus_parse_opts opts;
+ memset(&opts, 0, sizeof(opts));
+ opts.errbuf = errbuf;
+ opts.errbuf_size = sizeof(errbuf);
+ cfl_sds_t in_buf = read_file(CMT_TESTS_DATA_PATH "/issue_6534.txt");
+ size_t in_size = cfl_sds_len(in_buf);
+
+ const char expected[] =
+ "# HELP dotnet_jit_method_total Total number of methods compiled by the JIT compiler\n"
+ "# TYPE dotnet_jit_method_total counter\n"
+ "dotnet_jit_method_total 6476 0\n"
+ "# HELP dotnet_gc_collection_count_total Counts the number of garbage collections that have occurred, broken down by generation number and the reason for the collection.\n"
+ "# TYPE dotnet_gc_collection_count_total counter\n"
+ "dotnet_gc_collection_count_total{gc_generation=\"1\",gc_reason=\"alloc_small\"} 133 0\n"
+ "dotnet_gc_collection_count_total{gc_generation=\"0\",gc_reason=\"alloc_small\"} 618 0\n"
+ "dotnet_gc_collection_count_total{gc_generation=\"2\",gc_reason=\"alloc_small\"} 8 0\n"
+ "# HELP dotnet_collection_count_total GC collection count\n"
+ "# TYPE dotnet_collection_count_total counter\n"
+ "dotnet_collection_count_total{generation=\"0\"} 759 0\n"
+ "dotnet_collection_count_total{generation=\"2\"} 8 0\n"
+ "dotnet_collection_count_total{generation=\"1\"} 141 0\n"
+ "# HELP dotnet_threadpool_adjustments_total The total number of changes made to the size of the thread pool, labeled by the reason for change\n"
+ "# TYPE dotnet_threadpool_adjustments_total counter\n"
+ "dotnet_threadpool_adjustments_total{adjustment_reason=\"starvation\"} 957 0\n"
+ "dotnet_threadpool_adjustments_total{adjustment_reason=\"warmup\"} 4 0\n"
+ "dotnet_threadpool_adjustments_total{adjustment_reason=\"thread_timed_out\"} 2409 0\n"
+ "dotnet_threadpool_adjustments_total{adjustment_reason=\"climbing_move\"} 93458 0\n"
+ "# HELP process_cpu_seconds_total Total user and system CPU time spent in seconds.\n"
+ "# TYPE process_cpu_seconds_total counter\n"
+ "process_cpu_seconds_total 1450.8499999999999 0\n"
+ "# HELP dotnet_contention_total The number of locks contended\n"
+ "# TYPE dotnet_contention_total counter\n"
+ "dotnet_contention_total 6758 0\n"
+ "# HELP dotnet_contention_seconds_total The total amount of time spent contending locks\n"
+ "# TYPE dotnet_contention_seconds_total counter\n"
+ "dotnet_contention_seconds_total 1.0246322000000074 0\n"
+ "# HELP dotnet_internal_recycle_count prometheus-net.DotNetRuntime internal metric. Counts the number of times the underlying event listeners have been recycled\n"
+ "# TYPE dotnet_internal_recycle_count counter\n"
+ "dotnet_internal_recycle_count 3 0\n"
+ "# HELP dotnet_gc_allocated_bytes_total The total number of bytes allocated on the managed heap\n"
+ "# TYPE dotnet_gc_allocated_bytes_total counter\n"
+ "dotnet_gc_allocated_bytes_total{gc_heap=\"soh\"} 19853322336 0\n"
+ "# HELP dotnet_threadpool_throughput_total The total number of work items that have finished execution in the thread pool\n"
+ "# TYPE dotnet_threadpool_throughput_total counter\n"
+ "dotnet_threadpool_throughput_total 3381388 0\n"
+ "# HELP dotnet_exceptions_total Count of exceptions thrown, broken down by type\n"
+ "# TYPE dotnet_exceptions_total counter\n"
+ "dotnet_exceptions_total{type=\"System.Net.Http.HttpRequestException\"} 792 0\n"
+ "dotnet_exceptions_total{type=\"System.ObjectDisposedException\"} 11977 0\n"
+ "dotnet_exceptions_total{type=\"System.IO.DirectoryNotFoundException\"} 14 0\n"
+ "dotnet_exceptions_total{type=\"System.Net.Sockets.SocketException\"} 258287 0\n"
+ "dotnet_exceptions_total{type=\"Grpc.Core.RpcException\"} 72 0\n"
+ "# HELP dotnet_threadpool_num_threads The number of active threads in the thread pool\n"
+ "# TYPE dotnet_threadpool_num_threads gauge\n"
+ "dotnet_threadpool_num_threads 6 0\n"
+ "# HELP dotnet_gc_heap_size_bytes The current size of all heaps (only updated after a garbage collection)\n"
+ "# TYPE dotnet_gc_heap_size_bytes gauge\n"
+ "dotnet_gc_heap_size_bytes{gc_generation=\"0\"} 24 0\n"
+ "dotnet_gc_heap_size_bytes{gc_generation=\"loh\"} 550280 0\n"
+ "dotnet_gc_heap_size_bytes{gc_generation=\"2\"} 2625704 0\n"
+ "dotnet_gc_heap_size_bytes{gc_generation=\"1\"} 226944 0\n"
+ "# HELP dotnet_threadpool_timer_count The number of timers active\n"
+ "# TYPE dotnet_threadpool_timer_count gauge\n"
+ "dotnet_threadpool_timer_count 5 0\n"
+ "# HELP dotnet_gc_cpu_ratio The percentage of process CPU time spent running garbage collections\n"
+ "# TYPE dotnet_gc_cpu_ratio gauge\n"
+ "dotnet_gc_cpu_ratio 0 0\n"
+ "# HELP process_open_handles Number of open handles\n"
+ "# TYPE process_open_handles gauge\n"
+ "process_open_handles 264 0\n"
+ "# HELP dotnet_gc_pause_ratio The percentage of time the process spent paused for garbage collection\n"
+ "# TYPE dotnet_gc_pause_ratio gauge\n"
+ "dotnet_gc_pause_ratio 0 0\n"
+ "# HELP dotnet_jit_il_bytes Total bytes of IL compiled by the JIT compiler\n"
+ "# TYPE dotnet_jit_il_bytes gauge\n"
+ "dotnet_jit_il_bytes 487850 0\n"
+ "# HELP dotnet_gc_memory_total_available_bytes The upper limit on the amount of physical memory .NET can allocate to\n"
+ "# TYPE dotnet_gc_memory_total_available_bytes gauge\n"
+ "dotnet_gc_memory_total_available_bytes 805306368 0\n"
+ "# HELP dotnet_build_info Build information about prometheus-net.DotNetRuntime and the environment\n"
+ "# TYPE dotnet_build_info gauge\n"
+ "dotnet_build_info{version=\"4.2.4.0\",target_framework=\".NETCoreApp,Version=v6.0\",runtime_version=\".NET 6.0.11\",os_version=\"Linux 5.4.0-1094-azure #100~18.04.1-Ubuntu SMP Mon Oct 17 11:44:30 UTC 2022\",process_architecture=\"X64\",gc_mode=\"Workstation\"} 1 0\n"
+ "# HELP process_start_time_seconds Start time of the process since unix epoch in seconds.\n"
+ "# TYPE process_start_time_seconds gauge\n"
+ "process_start_time_seconds 1670526623.05 0\n"
+ "# HELP process_cpu_count The number of processor cores available to this process.\n"
+ "# TYPE process_cpu_count gauge\n"
+ "process_cpu_count 1 0\n"
+ "# HELP dotnet_gc_pinned_objects The number of pinned objects\n"
+ "# TYPE dotnet_gc_pinned_objects gauge\n"
+ "dotnet_gc_pinned_objects 0 0\n"
+ "# HELP dotnet_total_memory_bytes Total known allocated memory\n"
+ "# TYPE dotnet_total_memory_bytes gauge\n"
+ "dotnet_total_memory_bytes 20979896 0\n"
+ "# HELP process_virtual_memory_bytes Virtual memory size in bytes.\n"
+ "# TYPE process_virtual_memory_bytes gauge\n"
+ "process_virtual_memory_bytes 8562679808 0\n"
+ "# HELP process_working_set_bytes Process working set\n"
+ "# TYPE process_working_set_bytes gauge\n"
+ "process_working_set_bytes 135118848 0\n"
+ "# HELP process_num_threads Total number of threads\n"
+ "# TYPE process_num_threads gauge\n"
+ "process_num_threads 21 0\n"
+ "# HELP dotnet_gc_finalization_queue_length The number of objects waiting to be finalized\n"
+ "# TYPE dotnet_gc_finalization_queue_length gauge\n"
+ "dotnet_gc_finalization_queue_length 15 0\n"
+ "# HELP process_private_memory_bytes Process private memory size\n"
+ "# TYPE process_private_memory_bytes gauge\n"
+ "process_private_memory_bytes 247390208 0\n"
+ "# HELP dotnet_threadpool_queue_length Measures the queue length of the thread pool. Values greater than 0 indicate a backlog of work for the threadpool to process.\n"
+ "# TYPE dotnet_threadpool_queue_length histogram\n"
+ "dotnet_threadpool_queue_length_bucket{le=\"0.0\"} 321728 0\n"
+ "dotnet_threadpool_queue_length_bucket{le=\"1.0\"} 321733 0\n"
+ "dotnet_threadpool_queue_length_bucket{le=\"10.0\"} 321733 0\n"
+ "dotnet_threadpool_queue_length_bucket{le=\"100.0\"} 321733 0\n"
+ "dotnet_threadpool_queue_length_bucket{le=\"1000.0\"} 321733 0\n"
+ "dotnet_threadpool_queue_length_bucket{le=\"+Inf\"} 0 0\n"
+ "dotnet_threadpool_queue_length_sum 5 0\n"
+ "dotnet_threadpool_queue_length_count 321733 0\n"
+ "# HELP dotnet_gc_pause_seconds The amount of time execution was paused for garbage collection\n"
+ "# TYPE dotnet_gc_pause_seconds histogram\n"
+ "dotnet_gc_pause_seconds_bucket{le=\"0.001\"} 7 0\n"
+ "dotnet_gc_pause_seconds_bucket{le=\"0.01\"} 747 0\n"
+ "dotnet_gc_pause_seconds_bucket{le=\"0.05\"} 759 0\n"
+ "dotnet_gc_pause_seconds_bucket{le=\"0.1\"} 759 0\n"
+ "dotnet_gc_pause_seconds_bucket{le=\"0.5\"} 759 0\n"
+ "dotnet_gc_pause_seconds_bucket{le=\"1.0\"} 759 0\n"
+ "dotnet_gc_pause_seconds_bucket{le=\"10.0\"} 759 0\n"
+ "dotnet_gc_pause_seconds_bucket{le=\"+Inf\"} 0 0\n"
+ "dotnet_gc_pause_seconds_sum 1.3192573999999997 0\n"
+ "dotnet_gc_pause_seconds_count 759 0\n"
+ "# HELP dotnet_gc_collection_seconds The amount of time spent running garbage collections\n"
+ "# TYPE dotnet_gc_collection_seconds histogram\n"
+ "dotnet_gc_collection_seconds_bucket{le=\"0.001\",gc_generation=\"1\",gc_type=\"non_concurrent_gc\"} 3 0\n"
+ "dotnet_gc_collection_seconds_bucket{le=\"0.01\",gc_generation=\"1\",gc_type=\"non_concurrent_gc\"} 133 0\n"
+ "dotnet_gc_collection_seconds_bucket{le=\"0.05\",gc_generation=\"1\",gc_type=\"non_concurrent_gc\"} 133 0\n"
+ "dotnet_gc_collection_seconds_bucket{le=\"0.1\",gc_generation=\"1\",gc_type=\"non_concurrent_gc\"} 133 0\n"
+ "dotnet_gc_collection_seconds_bucket{le=\"0.5\",gc_generation=\"1\",gc_type=\"non_concurrent_gc\"} 133 0\n"
+ "dotnet_gc_collection_seconds_bucket{le=\"1.0\",gc_generation=\"1\",gc_type=\"non_concurrent_gc\"} 133 0\n"
+ "dotnet_gc_collection_seconds_bucket{le=\"10.0\",gc_generation=\"1\",gc_type=\"non_concurrent_gc\"} 133 0\n"
+ "dotnet_gc_collection_seconds_bucket{le=\"+Inf\",gc_generation=\"1\",gc_type=\"non_concurrent_gc\"} 0 0\n"
+ "dotnet_gc_collection_seconds_sum{gc_generation=\"1\",gc_type=\"non_concurrent_gc\"} 0.20421500000000006 0\n"
+ "dotnet_gc_collection_seconds_count{gc_generation=\"1\",gc_type=\"non_concurrent_gc\"} 133 0\n"
+ "# HELP dotnet_gc_collection_seconds The amount of time spent running garbage collections\n"
+ "# TYPE dotnet_gc_collection_seconds histogram\n"
+ "dotnet_gc_collection_seconds_bucket{le=\"0.001\"} 0 0\n"
+ "dotnet_gc_collection_seconds_bucket{le=\"0.01\"} 0 0\n"
+ "dotnet_gc_collection_seconds_bucket{le=\"0.05\"} 0 0\n"
+ "dotnet_gc_collection_seconds_bucket{le=\"0.1\"} 0 0\n"
+ "dotnet_gc_collection_seconds_bucket{le=\"0.5\"} 0 0\n"
+ "dotnet_gc_collection_seconds_bucket{le=\"1.0\"} 0 0\n"
+ "dotnet_gc_collection_seconds_bucket{le=\"10.0\"} 0 0\n"
+ "dotnet_gc_collection_seconds_bucket{le=\"+Inf\"} 0 0\n"
+ "dotnet_gc_collection_seconds_sum 0 0\n"
+ "dotnet_gc_collection_seconds_count 0 0\n"
+ "# HELP dotnet_gc_collection_seconds The amount of time spent running garbage collections\n"
+ "# TYPE dotnet_gc_collection_seconds histogram\n"
+ "dotnet_gc_collection_seconds_bucket{le=\"0.001\",gc_generation=\"2\",gc_type=\"non_concurrent_gc\"} 0 0\n"
+ "dotnet_gc_collection_seconds_bucket{le=\"0.01\",gc_generation=\"2\",gc_type=\"non_concurrent_gc\"} 0 0\n"
+ "dotnet_gc_collection_seconds_bucket{le=\"0.05\",gc_generation=\"2\",gc_type=\"non_concurrent_gc\"} 8 0\n"
+ "dotnet_gc_collection_seconds_bucket{le=\"0.1\",gc_generation=\"2\",gc_type=\"non_concurrent_gc\"} 8 0\n"
+ "dotnet_gc_collection_seconds_bucket{le=\"0.5\",gc_generation=\"2\",gc_type=\"non_concurrent_gc\"} 8 0\n"
+ "dotnet_gc_collection_seconds_bucket{le=\"1.0\",gc_generation=\"2\",gc_type=\"non_concurrent_gc\"} 8 0\n"
+ "dotnet_gc_collection_seconds_bucket{le=\"10.0\",gc_generation=\"2\",gc_type=\"non_concurrent_gc\"} 8 0\n"
+ "dotnet_gc_collection_seconds_bucket{le=\"+Inf\",gc_generation=\"2\",gc_type=\"non_concurrent_gc\"} 0 0\n"
+ "dotnet_gc_collection_seconds_sum{gc_generation=\"2\",gc_type=\"non_concurrent_gc\"} 0.093447800000000011 0\n"
+ "dotnet_gc_collection_seconds_count{gc_generation=\"2\",gc_type=\"non_concurrent_gc\"} 8 0\n"
+ "dotnet_gc_collection_seconds_bucket{le=\"0.001\",gc_generation=\"0\",gc_type=\"non_concurrent_gc\"} 127 0\n"
+ "dotnet_gc_collection_seconds_bucket{le=\"0.01\",gc_generation=\"0\",gc_type=\"non_concurrent_gc\"} 617 0\n"
+ "dotnet_gc_collection_seconds_bucket{le=\"0.05\",gc_generation=\"0\",gc_type=\"non_concurrent_gc\"} 618 0\n"
+ "dotnet_gc_collection_seconds_bucket{le=\"0.1\",gc_generation=\"0\",gc_type=\"non_concurrent_gc\"} 618 0\n"
+ "dotnet_gc_collection_seconds_bucket{le=\"0.5\",gc_generation=\"0\",gc_type=\"non_concurrent_gc\"} 618 0\n"
+ "dotnet_gc_collection_seconds_bucket{le=\"1.0\",gc_generation=\"0\",gc_type=\"non_concurrent_gc\"} 618 0\n"
+ "dotnet_gc_collection_seconds_bucket{le=\"10.0\",gc_generation=\"0\",gc_type=\"non_concurrent_gc\"} 618 0\n"
+ "dotnet_gc_collection_seconds_bucket{le=\"+Inf\",gc_generation=\"0\",gc_type=\"non_concurrent_gc\"} 0 0\n"
+ "dotnet_gc_collection_seconds_sum{gc_generation=\"0\",gc_type=\"non_concurrent_gc\"} 0.85545190000000104 0\n"
+ "dotnet_gc_collection_seconds_count{gc_generation=\"0\",gc_type=\"non_concurrent_gc\"} 618 0\n"
+ ;
+
+ status = cmt_decode_prometheus_create(&cmt, in_buf, in_size, &opts);
+ TEST_CHECK(status == 0);
+ if (status) {
+ fprintf(stderr, "PARSE ERROR:\n======\n%s\n======\n", errbuf);
+ }
+ else {
+ result = cmt_encode_prometheus_create(cmt, CMT_TRUE);
+ status = strcmp(result, expected);
+ TEST_CHECK(status == 0);
+ if (status) {
+ fprintf(stderr, "EXPECTED:\n======\n%s\n======\nRESULT:\n======\n%s\n======\n", expected, result);
+ }
+ }
+
+ cfl_sds_destroy(in_buf);
+ cfl_sds_destroy(result);
+ cmt_decode_prometheus_destroy(cmt);
+}
+
+TEST_LIST = {
+ {"header_help", test_header_help},
+ {"header_type", test_header_type},
+ {"header_help_type", test_header_help_type},
+ {"header_type_help", test_header_type_help},
+ {"labels", test_labels},
+ {"labels_trailing_comma", test_labels_trailing_comma},
+ {"sample", test_sample},
+ {"samples", test_samples},
+ {"escape_sequences", test_escape_sequences},
+ {"metric_without_labels", test_metric_without_labels},
+ {"prometheus_spec_example", test_prometheus_spec_example},
+ {"bison_parsing_error", test_bison_parsing_error},
+ {"label_limits", test_label_limits},
+ {"invalid_value", test_invalid_value},
+ {"invalid_timestamp", test_invalid_timestamp},
+ {"default_timestamp", test_default_timestamp},
+ {"values", test_values},
+ {"in_size", test_in_size},
+ {"issue_71", test_issue_71},
+ {"histogram", test_histogram},
+ {"histogram_labels", test_histogram_labels},
+ {"summary", test_summary},
+ {"null_labels", test_null_labels},
+ {"issue_fluent_bit_5541", test_issue_fluent_bit_5541},
+ {"issue_fluent_bit_5894", test_issue_fluent_bit_5894},
+ {"empty_metrics", test_empty_metrics},
+ {"issue_fluent_bit_6021", test_issue_fluent_bit_6021},
+ {"override_timestamp", test_override_timestamp},
+ {"pr_168", test_pr_168},
+ {"histogram_different_label_count", test_histogram_different_label_count},
+ {"issue_fluent_bit_6534", test_issue_fluent_bit_6534},
+ { 0 }
+};
diff --git a/src/fluent-bit/lib/cmetrics/tests/summary.c b/src/fluent-bit/lib/cmetrics/tests/summary.c
new file mode 100644
index 000000000..99ffe5792
--- /dev/null
+++ b/src/fluent-bit/lib/cmetrics/tests/summary.c
@@ -0,0 +1,172 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* CMetrics
+ * ========
+ * Copyright 2021-2022 The CMetrics Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cmetrics/cmetrics.h>
+#include <cmetrics/cmt_summary.h>
+#include <cmetrics/cmt_encode_prometheus.h>
+#include <cmetrics/cmt_encode_text.h>
+
+#include "cmt_tests.h"
+
+static void prometheus_encode_test(struct cmt *cmt)
+{
+ cfl_sds_t buf;
+
+ buf = cmt_encode_prometheus_create(cmt, CMT_FALSE);
+ printf("\n%s\n", buf);
+ cmt_encode_prometheus_destroy(buf);
+
+ /* encode to all possible formats */
+ cmt_test_encode_all(cmt);
+}
+
+void test_set_defaults()
+{
+ double sum;
+ uint64_t count;
+ uint64_t ts;
+ double q[6];
+ double r[6];
+ struct cmt *cmt;
+ struct cmt_summary *s;
+
+ cmt_initialize();
+
+ /* Timestamp */
+ ts = cfl_time_now();
+
+ /* CMetrics context */
+ cmt = cmt_create();
+ TEST_CHECK(cmt != NULL);
+
+ /* set quantiles, no labels */
+ q[0] = 0.1;
+ q[1] = 0.2;
+ q[2] = 0.3;
+ q[3] = 0.4;
+ q[4] = 0.5;
+ q[5] = 1.0;
+
+ r[0] = 1;
+ r[1] = 2;
+ r[2] = 3;
+ r[3] = 4;
+ r[4] = 5;
+ r[5] = 6;
+
+ /* Create a gauge metric type */
+ s = cmt_summary_create(cmt,
+ "k8s", "network", "load", "Network load",
+ 6, q,
+ 1, (char *[]) {"my_label"});
+ TEST_CHECK(s != NULL);
+
+ count = 10;
+ sum = 51.612894511314444;
+
+ /* no quantiles, no labels */
+ cmt_summary_set_default(s, ts, NULL, sum, count, 0, NULL);
+ prometheus_encode_test(cmt);
+
+ cmt_summary_set_default(s, ts, r, sum, count, 0, NULL);
+ prometheus_encode_test(cmt);
+
+ /* static label: register static label for the context */
+ cmt_label_add(cmt, "static", "test");
+ prometheus_encode_test(cmt);
+
+ cmt_destroy(cmt);
+}
+
+/* ref: https://github.com/fluent/fluent-bit/issues/5894 */
+void fluentbit_bug_5894()
+{
+ double sum;
+ uint64_t count;
+ uint64_t ts;
+ double q[6];
+ double r[6];
+ struct cmt *cmt;
+ struct cmt_summary *s;
+
+ cmt_initialize();
+
+ /* Timestamp */
+ ts = cfl_time_now();
+
+ /* CMetrics context */
+ cmt = cmt_create();
+ TEST_CHECK(cmt != NULL);
+
+ /* set quantiles, no labels */
+ q[0] = 0.1;
+ q[1] = 0.2;
+ q[2] = 0.3;
+ q[3] = 0.4;
+ q[4] = 0.5;
+ q[5] = 1.0;
+
+ r[0] = 1;
+ r[1] = 2;
+ r[2] = 3;
+ r[3] = 4;
+ r[4] = 5;
+ r[5] = 6;
+
+ /* Create a gauge metric type */
+ s = cmt_summary_create(cmt,
+ "spring", "kafka_listener", "seconds", "Kafka Listener Timer",
+ 6, q,
+ 3, (char *[]) {"exception", "name", "result"});
+ TEST_CHECK(s != NULL);
+
+ /* no quantiles, labels */
+ sum = 0.0;
+ count = 1;
+
+ cmt_summary_set_default(s, ts, NULL, sum, count,
+ 3, (char *[]) {"ListenerExecutionFailedException",
+ "org.springframework.kafka.KafkaListenerEndpointContainer#0-0",
+ "failure"});
+
+ /* no quantiles, labels */
+ sum = 0.1;
+ count = 2;
+ cmt_summary_set_default(s, ts, NULL, sum, count,
+ 3, (char *[]) {"none",
+ "org.springframework.kafka.KafkaListenerEndpointContainer#0-0",
+ "success"});
+
+ /* quantiles, labels */
+ sum = 0.2;
+ count = 3;
+ cmt_summary_set_default(s, ts, r, sum, count,
+ 3, (char *[]) {"extra test",
+ "org.springframework.kafka.KafkaListenerEndpointContainer#0-0",
+ "success"});
+
+ prometheus_encode_test(cmt);
+ cmt_destroy(cmt);
+}
+
+TEST_LIST = {
+ {"set_defaults" , test_set_defaults},
+ {"fluentbit_bug_5894", fluentbit_bug_5894},
+ { 0 }
+};
diff --git a/src/fluent-bit/lib/cmetrics/tests/untyped.c b/src/fluent-bit/lib/cmetrics/tests/untyped.c
new file mode 100644
index 000000000..83c3647fe
--- /dev/null
+++ b/src/fluent-bit/lib/cmetrics/tests/untyped.c
@@ -0,0 +1,122 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* CMetrics
+ * ========
+ * Copyright 2021-2022 The CMetrics Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cmetrics/cmetrics.h>
+#include <cmetrics/cmt_untyped.h>
+#include <cmetrics/cmt_encode_msgpack.h>
+#include <cmetrics/cmt_decode_msgpack.h>
+#include <cmetrics/cmt_encode_prometheus.h>
+#include <cmetrics/cmt_encode_text.h>
+#include <cmetrics/cmt_encode_influx.h>
+
+#include "cmt_tests.h"
+
+static struct cmt *generate_encoder_test_data()
+{
+ int ret;
+ double val;
+ uint64_t ts;
+ struct cmt *cmt;
+ struct cmt_untyped *u;
+
+ printf("version: %s\n\n", cmt_version());
+ cmt = cmt_create();
+
+ u = cmt_untyped_create(cmt, "kubernetes", "network", "load", "Network load",
+ 2, (char *[]) {"hostname", "app"});
+
+ ts = cfl_time_now();
+
+ ret = cmt_untyped_get_val(u, 0, NULL, &val);
+ TEST_CHECK(ret == -1);
+
+ cmt_untyped_set(u, ts, 2, 0, NULL);
+ cmt_untyped_get_val(u, 0, NULL, &val);
+ TEST_CHECK(val == 2.0);
+
+ cmt_untyped_get_val(u, 2, (char *[]) {"localhost", "cmetrics"}, &val);
+ cmt_untyped_get_val(u, 2, (char *[]) {"localhost", "test"}, &val);
+ cmt_untyped_set(u, ts, 12.15, 2, (char *[]) {"localhost", "test"});
+ cmt_untyped_set(u, ts, 1, 2, (char *[]) {"localhost", "test"});
+
+ return cmt;
+}
+
+void test_prometheus()
+{
+ struct cmt *cmt = NULL;
+ cfl_sds_t prom = NULL;
+
+ cmt_initialize();
+
+ cmt = generate_encoder_test_data();
+ TEST_CHECK(cmt != NULL);
+
+ prom = cmt_encode_prometheus_create(cmt, CMT_TRUE);
+ TEST_CHECK(prom != NULL);
+ printf("%s\n", prom);
+
+ cmt_encode_prometheus_destroy(prom);
+ cmt_destroy(cmt);
+}
+
+void test_text()
+{
+ struct cmt *cmt = NULL;
+ cfl_sds_t text = NULL;
+
+ cmt_initialize();
+
+ cmt = generate_encoder_test_data();
+ TEST_CHECK(cmt != NULL);
+
+ text = cmt_encode_text_create(cmt);
+ TEST_CHECK(text != NULL);
+
+ printf("%s\n", text);
+
+ cmt_encode_text_destroy(text);
+ cmt_destroy(cmt);
+}
+
+void test_influx()
+{
+ struct cmt *cmt = NULL;
+ cfl_sds_t text = NULL;
+
+ cmt_initialize();
+
+ cmt = generate_encoder_test_data();
+ TEST_CHECK(cmt != NULL);
+
+ text = cmt_encode_influx_create(cmt);
+ TEST_CHECK(text != NULL);
+
+ printf("%s\n", text);
+
+ cmt_encode_influx_destroy(text);
+ cmt_destroy(cmt);
+}
+
+TEST_LIST = {
+ {"prometheus", test_prometheus},
+ {"text" , test_text},
+ {"influx" , test_influx},
+ { 0 }
+};
diff --git a/src/fluent-bit/lib/cmetrics/tests/util.c b/src/fluent-bit/lib/cmetrics/tests/util.c
new file mode 100644
index 000000000..c8c9815e2
--- /dev/null
+++ b/src/fluent-bit/lib/cmetrics/tests/util.c
@@ -0,0 +1,68 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* CMetrics
+ * ========
+ * Copyright 2021-2022 The CMetrics Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+
+#include <cmetrics/cmetrics.h>
+
+cfl_sds_t read_file(const char *path)
+{
+ long flen;
+ FILE *f = NULL;
+ cfl_sds_t result = NULL;
+
+ f = fopen(path, "rb");
+ if (!f) {
+ return NULL;
+ }
+
+ if (fseek(f, 0, SEEK_END) == -1) {
+ goto err;
+ }
+
+ flen = ftell(f);
+ if (flen < 0) {
+ goto err;
+ }
+
+ if (fseek(f, 0, SEEK_SET) == -1) {
+ goto err;
+ }
+
+ result = cfl_sds_create_size(flen);
+ if (!result) {
+ goto err;
+ }
+
+ if (flen > 0 && fread(result, flen, 1, f) != 1) {
+ goto err;
+ }
+
+ cfl_sds_set_len(result, flen);
+ fclose(f);
+ return result;
+
+err:
+ fclose(f);
+ if (result) {
+ cfl_sds_destroy(result);
+ }
+ return NULL;
+}
+