MEDIUM: otel: wired OTel C wrapper library integration

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.
This commit is contained in:
Miroslav Zagorac 2026-04-12 10:52:53 +02:00 committed by William Lallemand
parent 2e962a5443
commit 3184470339
3 changed files with 179 additions and 4 deletions

View 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;

View File

@ -10,6 +10,121 @@
*/
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;

View 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);