]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: otel: added memory pool and runtime scope layer
authorMiroslav Zagorac <mzagorac@haproxy.com>
Sun, 12 Apr 2026 07:47:18 +0000 (09:47 +0200)
committerWilliam Lallemand <wlallemand@haproxy.com>
Mon, 13 Apr 2026 07:23:26 +0000 (09:23 +0200)
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
addons/otel/include/config.h
addons/otel/include/include.h
addons/otel/include/pool.h [new file with mode: 0644]
addons/otel/include/scope.h [new file with mode: 0644]
addons/otel/src/pool.c [new file with mode: 0644]
addons/otel/src/scope.c [new file with mode: 0644]

index 53c17b2fe57ccb5c023329ca70527c07b87c0303..c9f91952622c39368a179a00fe612e77269cbbf9 100644 (file)
@@ -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)
index 2f28c2a57e00c5930840cb32ff5754c25c7f517e..5532124f193559ccca068187b908974f4a173e60 100644 (file)
@@ -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. */
 
index e57d305a930050ad3cbc928750fe68e6c135cd20..e154cfeb1b9c7f03dcd2c6e75f28ae379990b05d 100644 (file)
@@ -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 (file)
index 0000000..300cafb
--- /dev/null
@@ -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 (file)
index 0000000..30e398e
--- /dev/null
@@ -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 (file)
index 0000000..dc1b077
--- /dev/null
@@ -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 <size> bytes of memory from the HAProxy memory <pool>.  If <pool>
+ *   is NULL, the allocation falls back to the heap via OTELC_MALLOC().  When
+ *   <flag_clear> is set, the allocated memory is zero-filled.  On allocation
+ *   failure, an error message is stored via <err>.
+ *
+ * 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 <size> characters from the string <s> using the HAProxy
+ *   memory <pool>.  If <pool> is NULL, the duplication falls back to
+ *   OTELC_STRNDUP().  When using a pool, the copy is truncated to <pool>->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 <pool>.  If
+ *   <pool> is NULL, the memory is freed via OTELC_SFREE().  The pointer <*ptr>
+ *   is set to NULL after freeing.  If <ptr> 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 <flag_clear> 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 (file)
index 0000000..0fe7801
--- /dev/null
@@ -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 <f>.  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 <id> string
+ *   ref_id     - the parent span or context name, or NULL
+ *   ref_id_len - length of the <ref_id> 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 <id> in the runtime context or creates a
+ *   new one.  If <ref_id> 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 <ptr> 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 <id> 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 <id> in the runtime context or creates
+ *   a new one by extracting the span context from the <text_map> carrier via
+ *   the <tracer>.
+ *
+ * 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 <ptr>.  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 <data> 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 <ptr> 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 <ptr>: 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 <chn>.
+ *
+ * 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
+ */