]> git.kaiwu.me - haproxy.git/commitdiff
MEDIUM: otel: wired OTel C wrapper library integration
authorMiroslav Zagorac <mzagorac@haproxy.com>
Sun, 12 Apr 2026 08:52:53 +0000 (10:52 +0200)
committerWilliam Lallemand <wlallemand@haproxy.com>
Mon, 13 Apr 2026 07:23:26 +0000 (09:23 +0200)
Connected the OpenTelemetry C wrapper library to the filter lifecycle by
implementing the library initialization, tracer creation, memory and
thread callbacks, shutdown sequence, and span completion.

The flt_otel_lib_init() function now verifies the C wrapper library
version against the compiled headers, calls otelc_init() with the absolute
configuration file path, and creates the tracer via otelc_tracer_create().
On success, it registers HAProxy pool-based memory callbacks
(flt_otel_mem_malloc, flt_otel_mem_free) and a thread ID callback
(flt_otel_thread_id) through otelc_ext_init(), so the C++ SDK allocates
span and context objects from pool_head_otel_span_context.  A custom log
handler (flt_otel_log_handler_cb) is registered via otelc_log_set_handler()
to count OTel SDK internal diagnostic messages in the flt_otel_drop_cnt
counter.

The per-thread init callback now starts the tracer thread via
OTELC_OPS(tracer, start) instead of unconditionally returning success.

The deinit callback saves the tracer handle before freeing the
configuration, then shuts down the library via otelc_deinit() after the
pool is destroyed, ensuring the ext callbacks remain valid while the
configuration structures are still being freed.  In debug builds, it logs
wrapper statistics, attach counters, and per-event HTX usage counters
before shutdown.

The runtime context cleanup in flt_otel_runtime_context_free() now ends
all active spans with a common monotonic timestamp via
OTELC_OPSR(span, end_with_options) before freeing them.  The scope context
cleanup in flt_otel_scope_context_free() now destroys the underlying OTel
span context via OTELC_OPSR(context, destroy).

The parser gained static storage for the debug memory tracker
(OTELC_DBG_MEM) and its initialization in the parse entry point, used when
compiled with the OTELC_DBG_MEM flag.

addons/otel/include/filter.h
addons/otel/src/filter.c
addons/otel/src/scope.c

index 86f2b835da79f26b9c0c2580738964a12d5b12b6..6ca82800a32093d06591d74cda452eb6df262fc9 100644 (file)
@@ -35,6 +35,7 @@ enum FLT_OTEL_RET_enum {
 
 
 extern const char     *otel_flt_id;
+extern uint64_t        flt_otel_drop_cnt;
 extern struct flt_ops  flt_otel_ops;
 
 
index 726a788f51dbccbb20d5ced6bde4ca2296668440..d50fa00ccba1c9d6e52221eb505892b626241f1e 100644 (file)
  */
 const char *otel_flt_id = "the OpenTelemetry filter";
 
+/* Counter of OTel SDK internal diagnostic messages. */
+uint64_t flt_otel_drop_cnt = 0;
+
+
+/***
+ * NAME
+ *   flt_otel_mem_malloc - OTel library memory allocator callback
+ *
+ * SYNOPSIS
+ *   static void *flt_otel_mem_malloc(const char *func, int line, size_t size)
+ *
+ * ARGUMENTS
+ *   func - caller function name (debug only)
+ *   line - caller source line number (debug only)
+ *   size - number of bytes to allocate
+ *
+ * DESCRIPTION
+ *   Allocator callback for the OpenTelemetry C wrapper library.  It allocates
+ *   the requested <size> bytes from the HAProxy pool_head_otel_span_context
+ *   pool.  This function is registered via otelc_ext_init().
+ *
+ * RETURN VALUE
+ *   Returns a pointer to the allocated memory, or NULL on failure.
+ */
+static void *flt_otel_mem_malloc(FLT_OTEL_DBG_ARGS(const char *func, int line, ) size_t size)
+{
+       return flt_otel_pool_alloc(pool_head_otel_span_context, size, 1, NULL);
+}
+
+
+/***
+ * NAME
+ *   flt_otel_mem_free - OTel library memory deallocator callback
+ *
+ * SYNOPSIS
+ *   static void flt_otel_mem_free(const char *func, int line, void *ptr)
+ *
+ * ARGUMENTS
+ *   func - caller function name (debug only)
+ *   line - caller source line number (debug only)
+ *   ptr  - pointer to the memory to free
+ *
+ * DESCRIPTION
+ *   Deallocator callback for the OpenTelemetry C wrapper library.  It returns
+ *   the memory pointed to by <ptr> back to the HAProxy
+ *   pool_head_otel_span_context pool.  This function is registered via
+ *   otelc_ext_init().
+ *
+ * RETURN VALUE
+ *   This function does not return a value.
+ */
+static void flt_otel_mem_free(FLT_OTEL_DBG_ARGS(const char *func, int line, ) void *ptr)
+{
+       flt_otel_pool_free(pool_head_otel_span_context, &ptr);
+}
+
+
+/***
+ * NAME
+ *   flt_otel_thread_id - OTel library thread ID callback
+ *
+ * SYNOPSIS
+ *   static int flt_otel_thread_id(void)
+ *
+ * ARGUMENTS
+ *   This function takes no arguments.
+ *
+ * DESCRIPTION
+ *   Thread ID callback for the OpenTelemetry C wrapper library.  It returns
+ *   the HAProxy thread identifier (tid).  This function is registered via
+ *   otelc_ext_init().
+ *
+ * RETURN VALUE
+ *   Returns the HAProxy thread ID.
+ */
+static int flt_otel_thread_id(void)
+{
+       return tid;
+}
+
+
+/***
+ * NAME
+ *   flt_otel_log_handler_cb - counts SDK internal diagnostic messages
+ *
+ * SYNOPSIS
+ *   static void flt_otel_log_handler_cb(otelc_log_level_t level, const char *file, int line, const char *msg, const struct otelc_kv *attr, size_t attr_len, void *ctx)
+ *
+ * ARGUMENTS
+ *   level    - severity of the OTel SDK diagnostic message
+ *   file     - source file that emitted the message
+ *   line     - source line number
+ *   msg      - formatted diagnostic message text
+ *   attr     - array of key-value attributes associated with the message
+ *   attr_len - number of entries in the attr array
+ *   ctx      - opaque context pointer (unused)
+ *
+ * DESCRIPTION
+ *   Custom OTel SDK internal log handler registered via otelc_log_set_handler().
+ *   Each invocation atomically increments the flt_otel_drop_cnt counter so the
+ *   HAProxy OTel filter can verify how many OTel SDK diagnostic messages were
+ *   emitted.  The message content is intentionally ignored.
+ *
+ * RETURN VALUE
+ *   This function does not return a value.
+ */
+static void flt_otel_log_handler_cb(otelc_log_level_t level __maybe_unused, const char *file __maybe_unused, int line __maybe_unused, const char *msg __maybe_unused, const struct otelc_kv *attr __maybe_unused, size_t attr_len __maybe_unused, void *ctx __maybe_unused)
+{
+       OTELC_FUNC("%d, \"%s\", %d, \"%s\", %p, %zu, %p", level, OTELC_STR_ARG(file), line, OTELC_STR_ARG(msg), attr, attr_len, ctx);
+
+       _HA_ATOMIC_INC(&flt_otel_drop_cnt);
+
+       OTELC_RETURN();
+}
+
 
 /***
  * NAME
@@ -39,6 +154,12 @@ static int flt_otel_lib_init(struct flt_otel_conf_instr *instr, char **err)
 
        OTELC_FUNC("%p, %p:%p", instr, OTELC_DPTR_ARGS(err));
 
+       if (!OTELC_IS_VALID_VERSION()) {
+               FLT_OTEL_ERR("OpenTelemetry C Wrapper version mismatch: library (%s) does not match header files (%s).  Please ensure both are the same version.", otelc_version(), OTELC_VERSION);
+
+               OTELC_RETURN_INT(retval);
+       }
+
        if (flt_otel_pool_init() == FLT_OTEL_RET_ERROR) {
                FLT_OTEL_ERR("failed to initialize memory pools");
 
@@ -60,7 +181,25 @@ static int flt_otel_lib_init(struct flt_otel_conf_instr *instr, char **err)
                OTELC_RETURN_INT(retval);
        }
 
-       retval = 0;
+       if (otelc_init(path, err) == OTELC_RET_ERROR) {
+               if (*err == NULL)
+                       FLT_OTEL_ERR("%s", "failed to initialize tracing library");
+
+               OTELC_RETURN_INT(retval);
+       }
+
+       instr->tracer = otelc_tracer_create(err);
+       if (instr->tracer == NULL) {
+               if (*err == NULL)
+                       FLT_OTEL_ERR("%s", "failed to initialize OpenTelemetry tracer");
+
+               OTELC_RETURN_INT(retval);
+       } else {
+               otelc_ext_init(flt_otel_mem_malloc, flt_otel_mem_free, flt_otel_thread_id);
+               otelc_log_set_handler(flt_otel_log_handler_cb, NULL, false);
+
+               retval = 0;
+       }
 
        OTELC_RETURN_INT(retval);
 }
@@ -266,6 +405,11 @@ static int flt_otel_ops_init(struct proxy *p, struct flt_conf *fconf)
 static void flt_otel_ops_deinit(struct proxy *p, struct flt_conf *fconf)
 {
        struct flt_otel_conf **conf = (fconf == NULL) ? NULL : (typeof(conf))&(fconf->conf);
+       struct otelc_tracer   *otel_tracer = NULL;
+#ifdef DEBUG_OTEL
+       char                   buffer[BUFSIZ];
+       int                    i;
+#endif
 
        OTELC_FUNC("%p, %p", p, fconf);
 
@@ -273,13 +417,32 @@ static void flt_otel_ops_deinit(struct proxy *p, struct flt_conf *fconf)
                OTELC_RETURN();
 
 #ifdef DEBUG_OTEL
+       otelc_statistics(buffer, sizeof(buffer));
+       OTELC_DBG(LOG, "%s", buffer);
+
 #  ifdef FLT_OTEL_USE_COUNTERS
        OTELC_DBG(LOG, "attach counters: %" PRIu64 " %" PRIu64 " %" PRIu64 " %" PRIu64, (*conf)->cnt.attached[0], (*conf)->cnt.attached[1], (*conf)->cnt.attached[2], (*conf)->cnt.attached[3]);
 #  endif
-#endif
+
+       OTELC_DBG(LOG, "--- used events ----------");
+       for (i = 0; i < OTELC_TABLESIZE((*conf)->cnt.event); i++)
+               if ((*conf)->cnt.event[i].flag_used)
+                       OTELC_DBG(LOG, "  %02d %25s: %" PRIu64 " / %" PRIu64, i, flt_otel_event_data[i].an_name, (*conf)->cnt.event[i].htx[0], (*conf)->cnt.event[i].htx[1]);
+#endif /* DEBUG_OTEL */
+
+       /*
+        * Save the OTel handles before freeing the configuration.
+        * flt_otel_conf_free() must run while the wrapper's ext callbacks
+        * still point to the HAProxy pool allocator; otelc_deinit() resets
+        * those callbacks, so it runs last.
+        */
+       if ((*conf)->instr != NULL)
+               otel_tracer = (*conf)->instr->tracer;
 
        flt_otel_conf_free(conf);
+       OTELC_MEMINFO();
        flt_otel_pool_destroy();
+       otelc_deinit(&otel_tracer, NULL, NULL);
 
        OTELC_RETURN();
 }
@@ -607,7 +770,9 @@ static int flt_otel_ops_init_per_thread(struct proxy *p, struct flt_conf *fconf)
         * filtering.
         */
        if (!(fconf->flags & FLT_CFG_FL_HTX)) {
-               retval = FLT_OTEL_RET_OK;
+               retval = OTELC_OPS(conf->instr->tracer, start);
+               if (retval == OTELC_RET_ERROR)
+                       FLT_OTEL_ALERT("%s", conf->instr->tracer->err);
 
                if (retval != FLT_OTEL_RET_ERROR)
                        fconf->flags |= FLT_CFG_FL_HTX;
index 0fe78013359bd216e141ffb1f14be8fe65b0f811..b0594d59e48806868747340f73449cc526473085 100644 (file)
@@ -87,10 +87,16 @@ void flt_otel_runtime_context_free(struct filter *f)
 
        /* End all active spans with a common timestamp. */
        if (!LIST_ISEMPTY(&(rt_ctx->spans))) {
+               struct timespec             ts_steady;
                struct flt_otel_scope_span *span, *span_back;
 
-               list_for_each_entry_safe(span, span_back, &(rt_ctx->spans), list)
+               /* All spans should be completed at the same time. */
+               (void)clock_gettime(CLOCK_MONOTONIC, &ts_steady);
+
+               list_for_each_entry_safe(span, span_back, &(rt_ctx->spans), list) {
+                       OTELC_OPSR(span->span, end_with_options, &ts_steady, OTELC_SPAN_STATUS_IGNORE, NULL);
                        flt_otel_scope_span_free(&span);
+               }
        }
 
        /* Destroy all extracted span contexts. */
@@ -323,6 +329,9 @@ void flt_otel_scope_context_free(struct flt_otel_scope_context **ptr)
 
        FLT_OTEL_DBG_SCOPE_CONTEXT("", *ptr);
 
+       if ((*ptr)->context != NULL)
+               OTELC_OPSR((*ptr)->context, destroy);
+
        FLT_OTEL_LIST_DEL(&((*ptr)->list));
        flt_otel_pool_free(pool_head_otel_scope_context, (void **)ptr);