From f05a6735b15235c6ae9f60c4ce9a0f888aed7c02 Mon Sep 17 00:00:00 2001 From: Miroslav Zagorac Date: Sun, 12 Apr 2026 09:47:18 +0200 Subject: [PATCH] MEDIUM: otel: added memory pool and runtime scope layer Added the memory pool management and the runtime scope layer that track per-stream OTel spans and contexts during request processing. The pool layer in pool.c manages HAProxy memory pools for the runtime structures used by the filter: scope spans, scope contexts, runtime contexts, and span contexts. Each pool is conditionally compiled via USE_POOL_OTEL_* macros defined in config.h and registered with REGISTER_POOL(). The allocation functions (flt_otel_pool_alloc, flt_otel_pool_strndup, flt_otel_pool_free) transparently fall back to heap allocation when the corresponding pool is not enabled. Trash buffer helpers (flt_otel_trash_alloc, flt_otel_trash_free) provide scratch space using either HAProxy's trash chunk pool or direct heap allocation. The scope layer in scope.c implements the per-stream runtime state. The flt_otel_runtime_context structure is allocated when a stream starts and holds the stream and filter references, hard-error/disabled/logging flags copied from the instrumentation configuration, idle timeout state, a generated UUID, and lists of active scope spans and extracted scope contexts. Scope spans (flt_otel_scope_span) carry the operation name, fetch direction, the OTel span handle, and optional parent references resolved from other spans or extracted contexts. Scope contexts (flt_otel_scope_context) hold an extracted span context obtained from a carrier text map via the tracer. The scope data structures (flt_otel_scope_data) aggregate growable key-value arrays for attributes and baggage, a linked list of named events with their own attribute arrays, and a span status code with description, representing the telemetry collected during a single event execution. --- addons/otel/Makefile | 2 + addons/otel/include/config.h | 8 + addons/otel/include/include.h | 2 + addons/otel/include/pool.h | 71 +++++ addons/otel/include/scope.h | 158 ++++++++++ addons/otel/src/pool.c | 385 +++++++++++++++++++++++++ addons/otel/src/scope.c | 525 ++++++++++++++++++++++++++++++++++ 7 files changed, 1151 insertions(+) create mode 100644 addons/otel/include/pool.h create mode 100644 addons/otel/include/scope.h create mode 100644 addons/otel/src/pool.c create mode 100644 addons/otel/src/scope.c diff --git a/addons/otel/Makefile b/addons/otel/Makefile index 53c17b2fe..c9f919526 100644 --- a/addons/otel/Makefile +++ b/addons/otel/Makefile @@ -55,6 +55,8 @@ OPTIONS_OBJS += \ addons/otel/src/event.o \ addons/otel/src/filter.o \ addons/otel/src/parser.o \ + addons/otel/src/pool.o \ + addons/otel/src/scope.o \ addons/otel/src/util.o OTEL_CFLAGS := $(OTEL_CFLAGS) -Iaddons/otel/include $(OTEL_DEFINE) diff --git a/addons/otel/include/config.h b/addons/otel/include/config.h index 2f28c2a57..5532124f1 100644 --- a/addons/otel/include/config.h +++ b/addons/otel/include/config.h @@ -3,6 +3,14 @@ #ifndef _OTEL_CONFIG_H_ #define _OTEL_CONFIG_H_ +/* Memory pool selection flags. */ +#define USE_POOL_BUFFER +#define USE_POOL_OTEL_SPAN_CONTEXT +#define USE_POOL_OTEL_SCOPE_SPAN +#define USE_POOL_OTEL_SCOPE_CONTEXT +#define USE_POOL_OTEL_RUNTIME_CONTEXT +#define USE_TRASH_CHUNK + #define FLT_OTEL_ID_MAXLEN 64 /* Maximum identifier length. */ #define FLT_OTEL_DEBUG_LEVEL 0b11101111111 /* Default debug bitmask. */ diff --git a/addons/otel/include/include.h b/addons/otel/include/include.h index e57d305a9..e154cfeb1 100644 --- a/addons/otel/include/include.h +++ b/addons/otel/include/include.h @@ -32,6 +32,8 @@ #include "conf_funcs.h" #include "filter.h" #include "parser.h" +#include "pool.h" +#include "scope.h" #include "util.h" #endif /* _OTEL_INCLUDE_H_ */ diff --git a/addons/otel/include/pool.h b/addons/otel/include/pool.h new file mode 100644 index 000000000..300cafb44 --- /dev/null +++ b/addons/otel/include/pool.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#ifndef _OTEL_POOL_H_ +#define _OTEL_POOL_H_ + +#define FLT_OTEL_POOL_INIT(p,n,s,r) \ + do { \ + if (((r) == FLT_OTEL_RET_OK) && ((p) == NULL)) { \ + (p) = create_pool(n, (s), MEM_F_SHARED); \ + if ((p) == NULL) \ + (r) = FLT_OTEL_RET_ERROR; \ + \ + OTELC_DBG(DEBUG, #p " %p %u", (p), FLT_OTEL_DEREF((p), size, 0)); \ + } \ + } while (0) + +#define FLT_OTEL_POOL_DESTROY(p) \ + do { \ + if ((p) != NULL) { \ + OTELC_DBG(DEBUG, #p " %p %u", (p), (p)->size); \ + \ + pool_destroy(p); \ + (p) = NULL; \ + } \ + } while (0) + + +extern struct pool_head *pool_head_otel_scope_span __read_mostly; +extern struct pool_head *pool_head_otel_scope_context __read_mostly; +extern struct pool_head *pool_head_otel_runtime_context __read_mostly; +extern struct pool_head *pool_head_otel_span_context __read_mostly; + + +/* Allocate memory from a pool with optional zeroing. */ +void *flt_otel_pool_alloc(struct pool_head *pool, size_t size, bool flag_clear, char **err); + +/* Duplicate a string into pool-allocated memory. */ +void *flt_otel_pool_strndup(struct pool_head *pool, const char *s, size_t size, char **err); + +/* Release pool-allocated memory and clear the pointer. */ +void flt_otel_pool_free(struct pool_head *pool, void **ptr); + +/* Initialize OTel filter memory pools. */ +int flt_otel_pool_init(void); + +/* Destroy OTel filter memory pools. */ +void flt_otel_pool_destroy(void); + +/* Log debug information about OTel filter memory pools. */ +#ifndef DEBUG_OTEL +# define flt_otel_pool_info() while (0) +#else +void flt_otel_pool_info(void); +#endif + +/* Allocate a trash buffer with optional zeroing. */ +struct buffer *flt_otel_trash_alloc(bool flag_clear, char **err); + +/* Release a trash buffer and clear the pointer. */ +void flt_otel_trash_free(struct buffer **ptr); + +#endif /* _OTEL_POOL_H_ */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + * + * vi: noexpandtab shiftwidth=8 tabstop=8 + */ diff --git a/addons/otel/include/scope.h b/addons/otel/include/scope.h new file mode 100644 index 000000000..30e398e13 --- /dev/null +++ b/addons/otel/include/scope.h @@ -0,0 +1,158 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#ifndef _OTEL_SCOPE_H_ +#define _OTEL_SCOPE_H_ + +#define FLT_OTEL_SCOPE_SPAN_FINISH_REQ "*req*" +#define FLT_OTEL_SCOPE_SPAN_FINISH_RES "*res*" +#define FLT_OTEL_SCOPE_SPAN_FINISH_ALL "*" + +#define FLT_OTEL_RT_CTX(p) ((struct flt_otel_runtime_context *)(p)) + +#define FLT_OTEL_DBG_SCOPE_SPAN(h,p) \ + OTELC_DBG(DEBUG, h "%p:{ '%s' %zu %u %hhu %p %p %p }", (p), \ + FLT_OTEL_STR_HDR_ARGS(p, id), (p)->smp_opt_dir, \ + (p)->flag_finish, (p)->span, (p)->ref_span, (p)->ref_ctx) + +#define FLT_OTEL_DBG_SCOPE_CONTEXT(h,p) \ + OTELC_DBG(DEBUG, h "%p:{ '%s' %zu %u %hhu %p }", (p), \ + FLT_OTEL_STR_HDR_ARGS(p, id), (p)->smp_opt_dir, \ + (p)->flag_finish, (p)->context) + +#define FLT_OTEL_DBG_SCOPE_DATA_EVENT(h,p) \ + OTELC_DBG(DEBUG, h "%p:{ '%s' %p %zu %zu %s }", &(p), \ + (p).name, (p).attr, (p).cnt, (p).size, \ + flt_otel_list_dump(&((p).list))) + +#define FLT_OTEL_DBG_SCOPE_DATA_STATUS(h,p) \ + OTELC_DBG(DEBUG, h "%p:{ %d '%s' }", (p), (p)->code, OTELC_STR_ARG((p)->description)) + +#define FLT_OTEL_DBG_SCOPE_DATA_KV_FMT "%p:{ %p %zu %zu }" +#define FLT_OTEL_DBG_SCOPE_DATA_KV_ARGS(p) &(p), (p).attr, (p).cnt, (p).size +#define FLT_OTEL_DBG_SCOPE_DATA(h,p) \ + OTELC_DBG(DEBUG, h "%p:{ " FLT_OTEL_DBG_SCOPE_DATA_KV_FMT " " FLT_OTEL_DBG_SCOPE_DATA_KV_FMT " %s }", (p), \ + FLT_OTEL_DBG_SCOPE_DATA_KV_ARGS((p)->baggage), FLT_OTEL_DBG_SCOPE_DATA_KV_ARGS((p)->attributes), \ + flt_otel_list_dump(&((p)->events))) + +#define FLT_OTEL_DBG_RUNTIME_CONTEXT(h,p) \ + OTELC_DBG(DEBUG, h "%p:{ %p %p '%s' %hhu %hhu 0x%02hhx 0x%08x %u %d %s %s }", (p), \ + (p)->stream, (p)->filter, (p)->uuid, (p)->flag_harderr, (p)->flag_disabled, \ + (p)->logging, (p)->analyzers, (p)->idle_timeout, (p)->idle_exp, \ + flt_otel_list_dump(&((p)->spans)), flt_otel_list_dump(&((p)->contexts))) + +/* Anonymous struct containing a const string pointer and its length. */ +#define FLT_OTEL_CONST_STR_HDR(p) \ + struct { \ + const char *p; \ + size_t p##_len; \ + } + + +/* Growable key-value array for span attributes or baggage. */ +struct flt_otel_scope_data_kv { + struct otelc_kv *attr; /* Key-value array for storing attributes. */ + size_t cnt; /* Number of currently used array elements. */ + size_t size; /* Total number of array elements. */ +}; + +/* Named event with its own key-value attribute array. */ +struct flt_otel_scope_data_event { + char *name; /* Event name, not used for other data types. */ + struct otelc_kv *attr; /* Key-value array for storing attributes. */ + size_t cnt; /* Number of currently used array elements. */ + size_t size; /* Total number of array elements. */ + struct list list; /* Used to chain this structure. */ +}; + +struct flt_otel_scope_data_status { + int code; /* OTELC_SPAN_STATUS_* value. */ + char *description; /* Span status description string. */ +}; + +struct flt_otel_scope_data { + struct flt_otel_scope_data_kv baggage; /* Defined scope baggage. */ + struct flt_otel_scope_data_kv attributes; /* Defined scope attributes. */ + struct list events; /* Defined scope events. */ + struct flt_otel_scope_data_status status; /* Defined scope status. */ +}; + +/* flt_otel_runtime_context->spans */ +struct flt_otel_scope_span { + FLT_OTEL_CONST_STR_HDR(id); /* The span operation name/len. */ + uint smp_opt_dir; /* SMP_OPT_DIR_RE(Q|S) */ + bool flag_finish; /* Whether the span is marked for completion. */ + struct otelc_span *span; /* The current span. */ + struct otelc_span *ref_span; /* Span to which the current span refers. */ + struct otelc_span_context *ref_ctx; /* Span context to which the current span refers. */ + struct list list; /* Used to chain this structure. */ +}; + +/* flt_otel_runtime_context->contexts */ +struct flt_otel_scope_context { + FLT_OTEL_CONST_STR_HDR(id); /* The span context name/len. */ + uint smp_opt_dir; /* SMP_OPT_DIR_RE(Q|S) */ + bool flag_finish; /* Whether the span context is marked for completion. */ + struct otelc_span_context *context; /* The current span context. */ + struct list list; /* Used to chain this structure. */ +}; + +/* The runtime filter context attached to a stream. */ +struct flt_otel_runtime_context { + struct stream *stream; /* The stream to which the filter is attached. */ + struct filter *filter; /* The OpenTelemetry filter. */ + char uuid[40]; /* Randomly generated UUID. */ + bool flag_harderr; /* [0 1] */ + bool flag_disabled; /* [0 1] */ + uint8_t logging; /* [0 1 3] */ + uint analyzers; /* Executed channel analyzers. */ + uint idle_timeout; /* Idle timeout interval in milliseconds (0 = off). */ + int idle_exp; /* Tick at which the next idle timeout fires. */ + struct list spans; /* The scope spans. */ + struct list contexts; /* The scope contexts. */ +}; + + +#ifndef DEBUG_OTEL +# define flt_otel_scope_data_dump(...) while (0) +#else +/* Dump scope data contents for debugging. */ +void flt_otel_scope_data_dump(const struct flt_otel_scope_data *data); +#endif + +/* Allocate and initialize a runtime context for a stream. */ +struct flt_otel_runtime_context *flt_otel_runtime_context_init(struct stream *s, struct filter *f, char **err); + +/* Free the runtime context attached to a filter. */ +void flt_otel_runtime_context_free(struct filter *f); + +/* Allocate and initialize a scope span in the runtime context. */ +struct flt_otel_scope_span *flt_otel_scope_span_init(struct flt_otel_runtime_context *rt_ctx, const char *id, size_t id_len, const char *ref_id, size_t ref_id_len, uint dir, char **err); + +/* Free a scope span and remove it from the runtime context. */ +void flt_otel_scope_span_free(struct flt_otel_scope_span **ptr); + +/* Allocate and initialize a scope context in the runtime context. */ +struct flt_otel_scope_context *flt_otel_scope_context_init(struct flt_otel_runtime_context *rt_ctx, struct otelc_tracer *tracer, const char *id, size_t id_len, const struct otelc_text_map *text_map, uint dir, char **err); + +/* Free a scope context and remove it from the runtime context. */ +void flt_otel_scope_context_free(struct flt_otel_scope_context **ptr); + +/* Initialize scope data arrays and lists. */ +void flt_otel_scope_data_init(struct flt_otel_scope_data *ptr); + +/* Free all scope data contents. */ +void flt_otel_scope_data_free(struct flt_otel_scope_data *ptr); + +/* Free scope spans and contexts no longer needed by a channel. */ +void flt_otel_scope_free_unused(struct flt_otel_runtime_context *rt_ctx, struct channel *chn); + +#endif /* _OTEL_SCOPE_H_ */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + * + * vi: noexpandtab shiftwidth=8 tabstop=8 + */ diff --git a/addons/otel/src/pool.c b/addons/otel/src/pool.c new file mode 100644 index 000000000..dc1b07716 --- /dev/null +++ b/addons/otel/src/pool.c @@ -0,0 +1,385 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "../include/include.h" + + +struct pool_head *pool_head_otel_scope_span __read_mostly = NULL; +struct pool_head *pool_head_otel_scope_context __read_mostly = NULL; +struct pool_head *pool_head_otel_runtime_context __read_mostly = NULL; +struct pool_head *pool_head_otel_span_context __read_mostly = NULL; + +#ifdef USE_POOL_OTEL_SCOPE_SPAN +REGISTER_POOL(&pool_head_otel_scope_span, "otel_scope_span", sizeof(struct flt_otel_scope_span)); +#endif +#ifdef USE_POOL_OTEL_SCOPE_CONTEXT +REGISTER_POOL(&pool_head_otel_scope_context, "otel_scope_context", sizeof(struct flt_otel_scope_context)); +#endif +#ifdef USE_POOL_OTEL_RUNTIME_CONTEXT +REGISTER_POOL(&pool_head_otel_runtime_context, "otel_runtime_context", sizeof(struct flt_otel_runtime_context)); +#endif +#ifdef USE_POOL_OTEL_SPAN_CONTEXT +REGISTER_POOL(&pool_head_otel_span_context, "otel_span_context", MAX(sizeof(struct otelc_span), sizeof(struct otelc_span_context))); +#endif + + +/*** + * NAME + * flt_otel_pool_alloc - pool-aware memory allocation + * + * SYNOPSIS + * void *flt_otel_pool_alloc(struct pool_head *pool, size_t size, bool flag_clear, char **err) + * + * ARGUMENTS + * pool - HAProxy memory pool to allocate from (or NULL for heap) + * size - number of bytes to allocate + * flag_clear - whether to zero-fill the allocated memory + * err - indirect pointer to error message string + * + * DESCRIPTION + * Allocates bytes of memory from the HAProxy memory . If + * is NULL, the allocation falls back to the heap via OTELC_MALLOC(). When + * is set, the allocated memory is zero-filled. On allocation + * failure, an error message is stored via . + * + * RETURN VALUE + * Returns a pointer to the allocated memory, or NULL on failure. + */ +void *flt_otel_pool_alloc(struct pool_head *pool, size_t size, bool flag_clear, char **err) +{ + void *retptr; + + OTELC_FUNC("%p, %zu, %hhu, %p:%p", pool, size, flag_clear, OTELC_DPTR_ARGS(err)); + + if (pool != NULL) { + retptr = pool_alloc(pool); + if (retptr != NULL) + OTELC_DBG(NOTICE, "POOL_ALLOC: %s:%d(%p %zu)", __func__, __LINE__, retptr, FLT_OTEL_DEREF(pool, size, size)); + } else { + retptr = OTELC_MALLOC(size); + } + + if (retptr == NULL) + FLT_OTEL_ERR("out of memory"); + else if (flag_clear) + (void)memset(retptr, 0, size); + + OTELC_RETURN_PTR(retptr); +} + + +/*** + * NAME + * flt_otel_pool_strndup - pool-aware string duplication + * + * SYNOPSIS + * void *flt_otel_pool_strndup(struct pool_head *pool, const char *s, size_t size, char **err) + * + * ARGUMENTS + * pool - HAProxy memory pool to allocate from (or NULL for heap) + * s - source string to duplicate + * size - maximum number of characters to copy + * err - indirect pointer to error message string + * + * DESCRIPTION + * Duplicates up to characters from the string using the HAProxy + * memory . If is NULL, the duplication falls back to + * OTELC_STRNDUP(). When using a pool, the copy is truncated to ->size-1 + * bytes and null-terminated. + * + * RETURN VALUE + * Returns a pointer to the duplicated string, or NULL on failure. + */ +void *flt_otel_pool_strndup(struct pool_head *pool, const char *s, size_t size, char **err) +{ + void *retptr; + + OTELC_FUNC("%p, \"%.*s\", %zu, %p:%p", pool, (int)size, s, size, OTELC_DPTR_ARGS(err)); + + if (pool != NULL) { + retptr = pool_alloc(pool); + if (retptr != NULL) { + (void)memcpy(retptr, s, MIN(pool->size - 1, size)); + + ((uint8_t *)retptr)[MIN(pool->size - 1, size)] = '\0'; + } + } else { + retptr = OTELC_STRNDUP(s, size); + } + + if (retptr != NULL) + OTELC_DBG(NOTICE, "POOL_STRNDUP: %s:%d(%p %zu)", __func__, __LINE__, retptr, FLT_OTEL_DEREF(pool, size, size)); + else + FLT_OTEL_ERR("out of memory"); + + OTELC_RETURN_PTR(retptr); +} + + +/*** + * NAME + * flt_otel_pool_free - pool-aware memory deallocation + * + * SYNOPSIS + * void flt_otel_pool_free(struct pool_head *pool, void **ptr) + * + * ARGUMENTS + * pool - HAProxy memory pool to return memory to (or NULL for heap) + * ptr - indirect pointer to the memory to free + * + * DESCRIPTION + * Returns memory referenced by <*ptr> to the HAProxy memory . If + * is NULL, the memory is freed via OTELC_SFREE(). The pointer <*ptr> + * is set to NULL after freeing. If is NULL or <*ptr> is already NULL, + * the function returns immediately. + * + * RETURN VALUE + * This function does not return a value. + */ +void flt_otel_pool_free(struct pool_head *pool, void **ptr) +{ + OTELC_FUNC("%p, %p:%p", pool, OTELC_DPTR_ARGS(ptr)); + + if ((ptr == NULL) || (*ptr == NULL)) + OTELC_RETURN(); + + OTELC_DBG(NOTICE, "POOL_FREE: %s:%d(%p %u)", __func__, __LINE__, *ptr, FLT_OTEL_DEREF(pool, size, 0)); + + if (pool != NULL) + pool_free(pool, *ptr); + else + OTELC_SFREE(*ptr); + + *ptr = NULL; + + OTELC_RETURN(); +} + + +/*** + * NAME + * flt_otel_pool_init - OTel filter memory pool initialization + * + * SYNOPSIS + * int flt_otel_pool_init(void) + * + * ARGUMENTS + * This function takes no arguments. + * + * DESCRIPTION + * Initializes all memory pools used by the OTel filter. Each pool is + * created only when the corresponding USE_POOL_OTEL_* macro is defined. + * + * RETURN VALUE + * Returns FLT_OTEL_RET_OK on success, FLT_OTEL_RET_ERROR on failure. + */ +int flt_otel_pool_init(void) +{ + int retval = FLT_OTEL_RET_OK; + + OTELC_FUNC(""); + +#ifdef USE_POOL_OTEL_SCOPE_SPAN + FLT_OTEL_POOL_INIT(pool_head_otel_scope_span, "otel_scope_span", sizeof(struct flt_otel_scope_span), retval); +#endif +#ifdef USE_POOL_OTEL_SCOPE_CONTEXT + FLT_OTEL_POOL_INIT(pool_head_otel_scope_context, "otel_scope_context", sizeof(struct flt_otel_scope_context), retval); +#endif +#ifdef USE_POOL_OTEL_RUNTIME_CONTEXT + FLT_OTEL_POOL_INIT(pool_head_otel_runtime_context, "otel_runtime_context", sizeof(struct flt_otel_runtime_context), retval); +#endif +#ifdef USE_POOL_OTEL_SPAN_CONTEXT + FLT_OTEL_POOL_INIT(pool_head_otel_span_context, "otel_span_context", OTELC_MAX(sizeof(struct otelc_span), sizeof(struct otelc_span_context)), retval); +#endif + + OTELC_RETURN_INT(retval); +} + + +/*** + * NAME + * flt_otel_pool_destroy - OTel filter memory pool destruction + * + * SYNOPSIS + * void flt_otel_pool_destroy(void) + * + * ARGUMENTS + * This function takes no arguments. + * + * DESCRIPTION + * Destroys all memory pools used by the OTel filter. Each pool is + * destroyed only when the corresponding USE_POOL_OTEL_* macro is defined. + * + * RETURN VALUE + * This function does not return a value. + */ +void flt_otel_pool_destroy(void) +{ + OTELC_FUNC(""); + +#ifdef USE_POOL_OTEL_SCOPE_SPAN + FLT_OTEL_POOL_DESTROY(pool_head_otel_scope_span); +#endif +#ifdef USE_POOL_OTEL_SCOPE_CONTEXT + FLT_OTEL_POOL_DESTROY(pool_head_otel_scope_context); +#endif +#ifdef USE_POOL_OTEL_RUNTIME_CONTEXT + FLT_OTEL_POOL_DESTROY(pool_head_otel_runtime_context); +#endif +#ifdef USE_POOL_OTEL_SPAN_CONTEXT + FLT_OTEL_POOL_DESTROY(pool_head_otel_span_context); +#endif + + OTELC_RETURN(); +} + + +#ifdef DEBUG_OTEL + +/*** + * NAME + * flt_otel_pool_info - debug pool sizes logging + * + * SYNOPSIS + * void flt_otel_pool_info(void) + * + * ARGUMENTS + * This function takes no arguments. + * + * DESCRIPTION + * Logs the sizes of all registered HAProxy memory pools used by the OTel + * filter (buffer, trash, scope_span, scope_context, runtime_context). + * + * RETURN VALUE + * This function does not return a value. + */ +void flt_otel_pool_info(void) +{ + OTELC_DBG(NOTICE, "--- pool info ----------"); + + /* + * In case we have some error in the configuration file, + * it is possible that this pool was not initialized. + */ +#ifdef USE_POOL_BUFFER + OTELC_DBG(NOTICE, " buffer: %p %u", pool_head_buffer, FLT_OTEL_DEREF(pool_head_buffer, size, 0)); +#endif +#ifdef USE_TRASH_CHUNK + OTELC_DBG(NOTICE, " trash: %p %u", pool_head_trash, FLT_OTEL_DEREF(pool_head_trash, size, 0)); +#endif + +#ifdef USE_POOL_OTEL_SCOPE_SPAN + OTELC_DBG(NOTICE, " otel_scope_span: %p %u", pool_head_otel_scope_span, FLT_OTEL_DEREF(pool_head_otel_scope_span, size, 0)); +#endif +#ifdef USE_POOL_OTEL_SCOPE_CONTEXT + OTELC_DBG(NOTICE, " otel_scope_context: %p %u", pool_head_otel_scope_context, FLT_OTEL_DEREF(pool_head_otel_scope_context, size, 0)); +#endif +#ifdef USE_POOL_OTEL_RUNTIME_CONTEXT + OTELC_DBG(NOTICE, " otel_runtime_context: %p %u", pool_head_otel_runtime_context, FLT_OTEL_DEREF(pool_head_otel_runtime_context, size, 0)); +#endif +#ifdef USE_POOL_OTEL_SPAN_CONTEXT + OTELC_DBG(NOTICE, " otel_span_context: %p %u", pool_head_otel_span_context, FLT_OTEL_DEREF(pool_head_otel_span_context, size, 0)); +#endif +} + +#endif /* DEBUG_OTEL */ + + +/*** + * NAME + * flt_otel_trash_alloc - trash buffer allocation + * + * SYNOPSIS + * struct buffer *flt_otel_trash_alloc(bool flag_clear, char **err) + * + * ARGUMENTS + * flag_clear - whether to zero-fill the buffer area + * err - indirect pointer to error message string + * + * DESCRIPTION + * Allocates a temporary buffer chunk for use as scratch space. When + * USE_TRASH_CHUNK is defined, the buffer is obtained via alloc_trash_chunk(); + * otherwise, a buffer structure and its data area are allocated from the heap + * using global.tune.bufsize as the buffer size. When is set, + * the buffer's data area is zero-filled. + * + * RETURN VALUE + * Returns a pointer to the allocated buffer, or NULL on failure. + */ +struct buffer *flt_otel_trash_alloc(bool flag_clear, char **err) +{ + struct buffer *retptr; + + OTELC_FUNC("%hhu, %p:%p", flag_clear, OTELC_DPTR_ARGS(err)); + +#ifdef USE_TRASH_CHUNK + retptr = alloc_trash_chunk(); + if (retptr != NULL) + OTELC_DBG(NOTICE, "TRASH_ALLOC: %s:%d(%p %zu)", __func__, __LINE__, retptr, retptr->size); +#else + retptr = OTELC_MALLOC(sizeof(*retptr)); + if (retptr != NULL) { + chunk_init(retptr, OTELC_MALLOC(global.tune.bufsize), global.tune.bufsize); + if (retptr->area == NULL) + OTELC_SFREE_CLEAR(retptr); + else + *(retptr->area) = '\0'; + } +#endif + + if (retptr == NULL) + FLT_OTEL_ERR("out of memory"); + else if (flag_clear) + (void)memset(retptr->area, 0, retptr->size); + + OTELC_RETURN_PTR(retptr); +} + + +/*** + * NAME + * flt_otel_trash_free - trash buffer deallocation + * + * SYNOPSIS + * void flt_otel_trash_free(struct buffer **ptr) + * + * ARGUMENTS + * ptr - indirect pointer to the buffer to free + * + * DESCRIPTION + * Frees a trash buffer chunk previously allocated by flt_otel_trash_alloc(). + * When USE_TRASH_CHUNK is defined, the buffer is freed via + * free_trash_chunk(); otherwise, both the data area and the buffer structure + * are freed individually. The pointer <*ptr> is set to NULL after freeing. + * + * RETURN VALUE + * This function does not return a value. + */ +void flt_otel_trash_free(struct buffer **ptr) +{ + OTELC_FUNC("%p:%p", OTELC_DPTR_ARGS(ptr)); + + if ((ptr == NULL) || (*ptr == NULL)) + OTELC_RETURN(); + + OTELC_DBG(NOTICE, "TRASH_FREE: %s:%d(%p %zu)", __func__, __LINE__, *ptr, (*ptr)->size); + +#ifdef USE_TRASH_CHUNK + free_trash_chunk(*ptr); +#else + OTELC_SFREE((*ptr)->area); + OTELC_SFREE(*ptr); +#endif + + *ptr = NULL; + + OTELC_RETURN(); +} + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + * + * vi: noexpandtab shiftwidth=8 tabstop=8 + */ diff --git a/addons/otel/src/scope.c b/addons/otel/src/scope.c new file mode 100644 index 000000000..0fe780133 --- /dev/null +++ b/addons/otel/src/scope.c @@ -0,0 +1,525 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "../include/include.h" + + +/*** + * NAME + * flt_otel_runtime_context_init - per-stream runtime context allocation + * + * SYNOPSIS + * struct flt_otel_runtime_context *flt_otel_runtime_context_init(struct stream *s, struct filter *f, char **err) + * + * ARGUMENTS + * s - the stream to which the context belongs + * f - the filter instance + * err - indirect pointer to error message string + * + * DESCRIPTION + * Allocates and initializes a per-stream runtime context from pool memory. + * It copies the hard-error, disabled and logging flags from the filter + * configuration, generates a UUID and stores it in the sess.otel.uuid + * HAProxy variable. + * + * RETURN VALUE + * Returns a pointer to the new runtime context, or NULL on failure. + */ +struct flt_otel_runtime_context *flt_otel_runtime_context_init(struct stream *s, struct filter *f, char **err) +{ + const struct flt_otel_conf *conf = FLT_OTEL_CONF(f); + struct buffer uuid; + struct flt_otel_runtime_context *retptr = NULL; + + OTELC_FUNC("%p, %p, %p:%p", s, f, OTELC_DPTR_ARGS(err)); + + retptr = flt_otel_pool_alloc(pool_head_otel_runtime_context, sizeof(*retptr), 1, err); + if (retptr == NULL) + OTELC_RETURN_PTR(retptr); + + /* Initialize runtime context fields and generate a session UUID. */ + retptr->stream = s; + retptr->filter = f; + retptr->flag_harderr = _HA_ATOMIC_LOAD(&(conf->instr->flag_harderr)); + retptr->flag_disabled = _HA_ATOMIC_LOAD(&(conf->instr->flag_disabled)); + retptr->logging = _HA_ATOMIC_LOAD(&(conf->instr->logging)); + retptr->idle_timeout = 0; + retptr->idle_exp = TICK_ETERNITY; + LIST_INIT(&(retptr->spans)); + LIST_INIT(&(retptr->contexts)); + + uuid = b_make(retptr->uuid, sizeof(retptr->uuid), 0, 0); + ha_generate_uuid_v4(&uuid); + + FLT_OTEL_DBG_RUNTIME_CONTEXT("session context: ", retptr); + + OTELC_RETURN_PTR(retptr); +} + + +/*** + * NAME + * flt_otel_runtime_context_free - per-stream runtime context cleanup + * + * SYNOPSIS + * void flt_otel_runtime_context_free(struct filter *f) + * + * ARGUMENTS + * f - the filter instance whose runtime context is to be freed + * + * DESCRIPTION + * Frees the per-stream runtime context attached to . It ends all active + * spans with the current monotonic timestamp, destroys all extracted + * contexts, and returns the pool memory. + * + * RETURN VALUE + * This function does not return a value. + */ +void flt_otel_runtime_context_free(struct filter *f) +{ + struct flt_otel_runtime_context *rt_ctx = f->ctx; + + OTELC_FUNC("%p", f); + + if (rt_ctx == NULL) + OTELC_RETURN(); + + FLT_OTEL_DBG_RUNTIME_CONTEXT("session context: ", rt_ctx); + + /* End all active spans with a common timestamp. */ + if (!LIST_ISEMPTY(&(rt_ctx->spans))) { + struct flt_otel_scope_span *span, *span_back; + + list_for_each_entry_safe(span, span_back, &(rt_ctx->spans), list) + flt_otel_scope_span_free(&span); + } + + /* Destroy all extracted span contexts. */ + if (!LIST_ISEMPTY(&(rt_ctx->contexts))) { + struct flt_otel_scope_context *ctx, *ctx_back; + + list_for_each_entry_safe(ctx, ctx_back, &(rt_ctx->contexts), list) + flt_otel_scope_context_free(&ctx); + } + + flt_otel_pool_free(pool_head_otel_runtime_context, &(f->ctx)); + + OTELC_RETURN(); +} + + +/*** + * NAME + * flt_otel_scope_span_init - scope span lookup or creation + * + * SYNOPSIS + * struct flt_otel_scope_span *flt_otel_scope_span_init(struct flt_otel_runtime_context *rt_ctx, const char *id, size_t id_len, const char *ref_id, size_t ref_id_len, uint dir, char **err) + * + * ARGUMENTS + * rt_ctx - the runtime context owning the span list + * id - the span operation name + * id_len - length of the string + * ref_id - the parent span or context name, or NULL + * ref_id_len - length of the string + * dir - the sample fetch direction (SMP_OPT_DIR_REQ/RES) + * err - indirect pointer to error message string + * + * DESCRIPTION + * Finds an existing scope span by in the runtime context or creates a + * new one. If is set, it resolves the parent reference by searching + * the span list first, then the extracted context list. + * + * RETURN VALUE + * Returns the existing or new scope span, or NULL on failure. + */ +struct flt_otel_scope_span *flt_otel_scope_span_init(struct flt_otel_runtime_context *rt_ctx, const char *id, size_t id_len, const char *ref_id, size_t ref_id_len, uint dir, char **err) +{ + struct otelc_span *ref_span = NULL; + struct otelc_span_context *ref_ctx = NULL; + struct flt_otel_scope_span *span, *retptr = NULL; + struct flt_otel_scope_context *ctx; + + OTELC_FUNC("%p, \"%s\", %zu, \"%s\", %zu, %u, %p:%p", rt_ctx, OTELC_STR_ARG(id), id_len, OTELC_STR_ARG(ref_id), ref_id_len, dir, OTELC_DPTR_ARGS(err)); + + if ((rt_ctx == NULL) || (id == NULL)) + OTELC_RETURN_PTR(retptr); + + /* Return the existing span if one matches this ID. */ + list_for_each_entry(span, &(rt_ctx->spans), list) + if (FLT_OTEL_CONF_STR_CMP(span->id, id)) { + OTELC_DBG(NOTICE, "found span %p", span); + + OTELC_RETURN_PTR(span); + } + + /* Resolve the parent reference from spans or contexts. */ + if (ref_id != NULL) { + list_for_each_entry(span, &(rt_ctx->spans), list) + if (FLT_OTEL_CONF_STR_CMP(span->id, ref_id)) { + ref_span = span->span; + + break; + } + + if (ref_span != NULL) { + OTELC_DBG(NOTICE, "found referenced span %p", span); + } else { + list_for_each_entry(ctx, &(rt_ctx->contexts), list) + if (FLT_OTEL_CONF_STR_CMP(ctx->id, ref_id)) { + ref_ctx = ctx->context; + + break; + } + + if (ref_ctx != NULL) { + OTELC_DBG(NOTICE, "found referenced context %p", ctx); + } else { + FLT_OTEL_ERR("cannot find referenced span/context '%s'", ref_id); + + OTELC_RETURN_PTR(retptr); + } + } + } + + retptr = flt_otel_pool_alloc(pool_head_otel_scope_span, sizeof(*retptr), 1, err); + if (retptr == NULL) + OTELC_RETURN_PTR(retptr); + + /* Populate the new scope span and insert it into the list. */ + retptr->id = id; + retptr->id_len = id_len; + retptr->smp_opt_dir = dir; + retptr->ref_span = ref_span; + retptr->ref_ctx = ref_ctx; + LIST_INSERT(&(rt_ctx->spans), &(retptr->list)); + + FLT_OTEL_DBG_SCOPE_SPAN("new span ", retptr); + + OTELC_RETURN_PTR(retptr); +} + + +/*** + * NAME + * flt_otel_scope_span_free - scope span cleanup + * + * SYNOPSIS + * void flt_otel_scope_span_free(struct flt_otel_scope_span **ptr) + * + * ARGUMENTS + * ptr - pointer to the scope span pointer to free + * + * DESCRIPTION + * Frees a scope span entry pointed to by and removes it from its list. + * If the OTel span is still active (non-NULL), the function refuses to free + * it and returns immediately. + * + * RETURN VALUE + * This function does not return a value. + */ +void flt_otel_scope_span_free(struct flt_otel_scope_span **ptr) +{ + OTELC_FUNC("%p:%p", OTELC_DPTR_ARGS(ptr)); + + if ((ptr == NULL) || (*ptr == NULL)) + OTELC_RETURN(); + + FLT_OTEL_DBG_SCOPE_SPAN("", *ptr); + + /* If the span is still active, do nothing. */ + if ((*ptr)->span != NULL) { + OTELC_DBG(NOTICE, "cannot finish active span"); + + OTELC_RETURN(); + } + + FLT_OTEL_LIST_DEL(&((*ptr)->list)); + flt_otel_pool_free(pool_head_otel_scope_span, (void **)ptr); + + OTELC_RETURN(); +} + + +/*** + * NAME + * flt_otel_scope_context_init - scope context extraction + * + * SYNOPSIS + * struct flt_otel_scope_context *flt_otel_scope_context_init(struct flt_otel_runtime_context *rt_ctx, struct otelc_tracer *tracer, const char *id, size_t id_len, const struct otelc_text_map *text_map, uint dir, char **err) + * + * ARGUMENTS + * rt_ctx - the runtime context owning the context list + * tracer - the OTel tracer used for context extraction + * id - the context name + * id_len - length of the string + * text_map - the carrier text map to extract from + * dir - the sample fetch direction (SMP_OPT_DIR_REQ/RES) + * err - indirect pointer to error message string + * + * DESCRIPTION + * Finds an existing scope context by in the runtime context or creates + * a new one by extracting the span context from the carrier via + * the . + * + * RETURN VALUE + * Returns the existing or new scope context, or NULL on failure. + */ +struct flt_otel_scope_context *flt_otel_scope_context_init(struct flt_otel_runtime_context *rt_ctx, struct otelc_tracer *tracer, const char *id, size_t id_len, const struct otelc_text_map *text_map, uint dir, char **err) +{ + struct flt_otel_scope_context *retptr = NULL; + + OTELC_FUNC("%p, %p, \"%s\", %zu, %p, %u, %p:%p", rt_ctx, tracer, OTELC_STR_ARG(id), id_len, text_map, dir, OTELC_DPTR_ARGS(err)); + + if ((rt_ctx == NULL) || (tracer == NULL) || (id == NULL) || (text_map == NULL)) + OTELC_RETURN_PTR(retptr); + + /* Return the existing context if one matches this ID. */ + list_for_each_entry(retptr, &(rt_ctx->contexts), list) + if (FLT_OTEL_CONF_STR_CMP(retptr->id, id)) { + OTELC_DBG(NOTICE, "found context %p", retptr); + + OTELC_RETURN_PTR(retptr); + } + + retptr = flt_otel_pool_alloc(pool_head_otel_scope_context, sizeof(*retptr), 1, err); + if (retptr == NULL) + OTELC_RETURN_PTR(retptr); + + /* Populate the new scope context and insert it into the list. */ + retptr->id = id; + retptr->id_len = id_len; + retptr->smp_opt_dir = dir; + LIST_INSERT(&(rt_ctx->contexts), &(retptr->list)); + + FLT_OTEL_DBG_SCOPE_CONTEXT("new context ", retptr); + + OTELC_RETURN_PTR(retptr); +} + + +/*** + * NAME + * flt_otel_scope_context_free - scope context cleanup + * + * SYNOPSIS + * void flt_otel_scope_context_free(struct flt_otel_scope_context **ptr) + * + * ARGUMENTS + * ptr - pointer to the scope context pointer to free + * + * DESCRIPTION + * Frees a scope context entry pointed to by . It destroys the + * underlying OTel span context, removes the entry from its list, and + * returns the pool memory. + * + * RETURN VALUE + * This function does not return a value. + */ +void flt_otel_scope_context_free(struct flt_otel_scope_context **ptr) +{ + OTELC_FUNC("%p:%p", OTELC_DPTR_ARGS(ptr)); + + if ((ptr == NULL) || (*ptr == NULL)) + OTELC_RETURN(); + + FLT_OTEL_DBG_SCOPE_CONTEXT("", *ptr); + + FLT_OTEL_LIST_DEL(&((*ptr)->list)); + flt_otel_pool_free(pool_head_otel_scope_context, (void **)ptr); + + OTELC_RETURN(); +} + + +#ifdef DEBUG_OTEL + +/*** + * NAME + * flt_otel_scope_data_dump - debug scope data dump + * + * SYNOPSIS + * void flt_otel_scope_data_dump(const struct flt_otel_scope_data *data) + * + * ARGUMENTS + * data - the scope data structure to dump + * + * DESCRIPTION + * Dumps the contents of a scope structure for debugging: baggage + * key-value pairs, attributes, events with their attributes, span links, + * and the status code/description. + * + * RETURN VALUE + * This function does not return a value. + */ +void flt_otel_scope_data_dump(const struct flt_otel_scope_data *data) +{ + size_t i; + + if (data == NULL) + return; + + if (data->baggage.attr == NULL) { + OTELC_DBG(WORKER, "baggage %p:{ }", &(data->baggage)); + } else { + OTELC_DBG(WORKER, "baggage %p:{", &(data->baggage)); + for (i = 0; i < data->baggage.cnt; i++) + OTELC_DBG_KV(WORKER, " ", data->baggage.attr + i); + OTELC_DBG(WORKER, "}"); + } + + if (data->attributes.attr == NULL) { + OTELC_DBG(WORKER, "attributes %p:{ }", &(data->attributes)); + } else { + OTELC_DBG(WORKER, "attributes %p:{", &(data->attributes)); + for (i = 0; i < data->attributes.cnt; i++) + OTELC_DBG_KV(WORKER, " ", data->attributes.attr + i); + OTELC_DBG(WORKER, "}"); + } + + if (LIST_ISEMPTY(&(data->events))) { + OTELC_DBG(WORKER, "events %p:{ }", &(data->events)); + } else { + struct flt_otel_scope_data_event *event; + + OTELC_DBG(WORKER, "events %p:{", &(data->events)); + list_for_each_entry_rev(event, &(data->events), list) { + OTELC_DBG(WORKER, " '%s' %zu/%zu", event->name, event->cnt, event->size); + if (event->attr != NULL) + for (i = 0; i < event->cnt; i++) + OTELC_DBG_KV(WORKER, " ", event->attr + i); + } + OTELC_DBG(WORKER, "}"); + } + + if ((data->status.code == 0) && (data->status.description == NULL)) + OTELC_DBG(WORKER, "status %p:{ }", &(data->status)); + else + FLT_OTEL_DBG_SCOPE_DATA_STATUS("status ", &(data->status)); +} + +#endif /* DEBUG_OTEL */ + + +/*** + * NAME + * flt_otel_scope_data_init - scope data zero-initialization + * + * SYNOPSIS + * void flt_otel_scope_data_init(struct flt_otel_scope_data *ptr) + * + * ARGUMENTS + * ptr - the scope data structure to initialize + * + * DESCRIPTION + * Zero-initializes the scope data structure pointed to by and sets up + * the event and link list heads. + * + * RETURN VALUE + * This function does not return a value. + */ +void flt_otel_scope_data_init(struct flt_otel_scope_data *ptr) +{ + OTELC_FUNC("%p", ptr); + + if (ptr == NULL) + OTELC_RETURN(); + + (void)memset(ptr, 0, sizeof(*ptr)); + LIST_INIT(&(ptr->events)); + + OTELC_RETURN(); +} + + +/*** + * NAME + * flt_otel_scope_data_free - scope data cleanup + * + * SYNOPSIS + * void flt_otel_scope_data_free(struct flt_otel_scope_data *ptr) + * + * ARGUMENTS + * ptr - the scope data structure to free + * + * DESCRIPTION + * Frees all contents of the scope data structure pointed to by : baggage + * and attribute key-value arrays, event entries with their attributes, link + * entries, and the status description string. + * + * RETURN VALUE + * This function does not return a value. + */ +void flt_otel_scope_data_free(struct flt_otel_scope_data *ptr) +{ + struct flt_otel_scope_data_event *event, *event_back; + + OTELC_FUNC("%p", ptr); + + if (ptr == NULL) + OTELC_RETURN(); + + FLT_OTEL_DBG_SCOPE_DATA("", ptr); + + /* Destroy all dynamic scope data contents. */ + otelc_kv_destroy(&(ptr->baggage.attr), ptr->baggage.cnt); + otelc_kv_destroy(&(ptr->attributes.attr), ptr->attributes.cnt); + list_for_each_entry_safe(event, event_back, &(ptr->events), list) { + otelc_kv_destroy(&(event->attr), event->cnt); + OTELC_SFREE(event->name); + OTELC_SFREE(event); + } + OTELC_SFREE(ptr->status.description); + + (void)memset(ptr, 0, sizeof(*ptr)); + + OTELC_RETURN(); +} + + +/*** + * NAME + * flt_otel_scope_free_unused - remove unused spans and contexts + * + * SYNOPSIS + * void flt_otel_scope_free_unused(struct flt_otel_runtime_context *rt_ctx, struct channel *chn) + * + * ARGUMENTS + * rt_ctx - the runtime context to clean up + * chn - the channel for HTTP header cleanup + * + * DESCRIPTION + * Removes scope spans with a NULL OTel span and scope contexts with a NULL + * OTel context from the runtime context. For each removed context, the + * associated HTTP headers are also cleaned up via . + * + * RETURN VALUE + * This function does not return a value. + */ +void flt_otel_scope_free_unused(struct flt_otel_runtime_context *rt_ctx, struct channel *chn) +{ + OTELC_FUNC("%p, %p", rt_ctx, chn); + + if (rt_ctx == NULL) + OTELC_RETURN(); + + /* Remove spans that were never successfully created. */ + if (!LIST_ISEMPTY(&(rt_ctx->spans))) { + struct flt_otel_scope_span *span, *span_back; + + list_for_each_entry_safe(span, span_back, &(rt_ctx->spans), list) + if (span->span == NULL) + flt_otel_scope_span_free(&span); + } + + FLT_OTEL_DBG_RUNTIME_CONTEXT("session context: ", rt_ctx); + + OTELC_RETURN(); +} + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + * + * vi: noexpandtab shiftwidth=8 tabstop=8 + */