]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: otel: added prefix-based variable scanning
authorMiroslav Zagorac <mzagorac@haproxy.com>
Wed, 11 Feb 2026 05:55:44 +0000 (06:55 +0100)
committerWilliam Lallemand <wlallemand@haproxy.com>
Mon, 13 Apr 2026 07:23:26 +0000 (09:23 +0200)
Introduced an alternative variable scanning strategy that directly walked
the CEB tree of HAProxy's variable store instead of maintaining a separate
tracking buffer.

The Makefile auto-detected whether struct var carried a "name" member
by inspecting include/haproxy/vars-t.h and conditionally defined
USE_OTEL_VARS_NAME.  When enabled, the tracking buffer (flt_otel_ctx) and
its callback type were compiled out and replaced by direct tree walks.

flt_otel_vars_unset() walked the CEB tree for the resolved scope, removed
every variable whose normalized name matched the given prefix followed by
a dot, and adjusted the variable accounting.  flt_otel_vars_get()
performed the same prefix scan under a read lock, denormalized each
matching variable name back to its original OTel form, and assembled the
results into an otelc_text_map.

A helper flt_otel_vars_get_scope() was added to resolve scope name strings
("txn", "sess", "proc", "req", "res") to the corresponding HAProxy
variable store.  The set path skipped the tracking buffer update when
prefix scanning was available.

addons/otel/Makefile
addons/otel/include/vars.h
addons/otel/src/vars.c

index 96a5c488c023dcc137a0565f39625480c038fdc5..74af20708016a2e9eb9e9968fa64bc2875a73a69 100644 (file)
@@ -65,6 +65,14 @@ OPTIONS_OBJS += \
 ifneq ($(OTEL_USE_VARS:0=),)
 OTEL_DEFINE  += -DUSE_OTEL_VARS
 OPTIONS_OBJS += addons/otel/src/vars.o
+
+# Auto-detect whether struct var has a 'name' member.  When present,
+# prefix-based variable scanning can be used instead of the tracking
+# buffer approach.
+OTEL_VAR_HAS_NAME := $(shell awk '/^struct var \{/,/^\}/' include/haproxy/vars-t.h 2>/dev/null | grep -q '[*]name;' && echo 1)
+ifneq ($(OTEL_VAR_HAS_NAME),)
+OTEL_DEFINE += -DUSE_OTEL_VARS_NAME
+endif
 endif
 
 OTEL_CFLAGS := $(OTEL_CFLAGS) -Iaddons/otel/include $(OTEL_DEFINE)
index 969dbc6dd444fcb5ce3e155c638684186541bd2d..d6bde500e1d4de797e66b05c9332cddb373fe5b7 100644 (file)
@@ -4,10 +4,12 @@
 #define _OTEL_VARS_H_
 
 #define FLT_OTEL_VARS_SCOPE       "txn"
-#define FLT_OTEL_VAR_CTX_SIZE     int8_t
 #define FLT_OTEL_VAR_CHAR_DASH    'D'
 #define FLT_OTEL_VAR_CHAR_SPACE   'S'
 
+#ifndef USE_OTEL_VARS_NAME
+#  define FLT_OTEL_VAR_CTX_SIZE   int8_t
+
 /* Context buffer for storing a single variable value during iteration. */
 struct flt_otel_ctx {
        char value[BUFSIZ]; /* Variable value string. */
@@ -16,6 +18,7 @@ struct flt_otel_ctx {
 
 /* Callback type invoked for each context variable during iteration. */
 typedef int (*flt_otel_ctx_loop_cb)(struct sample *, size_t, const char *, const char *, const char *, FLT_OTEL_VAR_CTX_SIZE, char **, void *);
+#endif /* !USE_OTEL_VARS_NAME */
 
 
 #ifndef DEBUG_OTEL
index ea395a84dfa28df4aefcc72d3e7549f6eb5098b9..dcb6e7c5ac91a7a87583f7dba99ef610cbbcdba2 100644 (file)
@@ -31,6 +31,7 @@ static void flt_otel_vars_scope_dump(struct vars *vars, const char *scope)
        if (vars == NULL)
                return;
 
+       /* Lock the variable store for safe iteration. */
        vars_rdlock(vars);
        for (i = 0; i < VAR_NAME_ROOTS; i++) {
                struct ceb_node *node = cebu64_imm_first(&(vars->name_root[i]));
@@ -307,6 +308,8 @@ static inline void flt_otel_smp_init(struct stream *s, struct sample *smp, uint
 }
 
 
+#ifndef USE_OTEL_VARS_NAME
+
 /***
  * NAME
  *   flt_otel_smp_add - context variable name registration
@@ -837,6 +840,221 @@ struct otelc_text_map *flt_otel_vars_get(struct stream *s, const char *scope, co
        OTELC_RETURN_PTR(retptr);
 }
 
+#else
+
+/***
+ * NAME
+ *   flt_otel_vars_get_scope - resolve scope string to variable store
+ *
+ * SYNOPSIS
+ *   static struct vars *flt_otel_vars_get_scope(struct stream *s, const char *scope)
+ *
+ * ARGUMENTS
+ *   s     - current stream
+ *   scope - variable scope string ("proc", "sess", "txn", "req", "res")
+ *
+ * DESCRIPTION
+ *   Resolves a scope name string to the corresponding HAProxy variable
+ *   store for the given <stream>.
+ *
+ * RETURN VALUE
+ *   Returns a pointer to the variable store, or NULL if the <scope>
+ *   is unknown.
+ */
+static struct vars *flt_otel_vars_get_scope(struct stream *s, const char *scope)
+{
+       if (strcmp(scope, "txn") == 0)
+               return &(s->vars_txn);
+       else if (strcmp(scope, "req") == 0)
+               return &(s->vars_reqres);
+       else if (strcmp(scope, "res") == 0)
+               return &(s->vars_reqres);
+       else if (strcmp(scope, "sess") == 0)
+               return &(s->sess->vars);
+       else if (strcmp(scope, "proc") == 0)
+               return &proc_vars;
+
+       return NULL;
+}
+
+
+/***
+ * NAME
+ *   flt_otel_vars_unset - context variables bulk unset via prefix scan
+ *
+ * SYNOPSIS
+ *   int flt_otel_vars_unset(struct stream *s, const char *scope, const char *prefix, uint opt, char **err)
+ *
+ * ARGUMENTS
+ *   s      - current stream
+ *   scope  - variable scope
+ *   prefix - variable prefix
+ *   opt    - sample option flags
+ *   err    - indirect pointer to error message string
+ *
+ * DESCRIPTION
+ *   Unsets all context variables whose name starts with the normalized
+ *   <prefix> followed by a dot.  Walks the CEB tree of the variable
+ *   store for the given <scope> and removes each matching variable.
+ *
+ * RETURN VALUE
+ *   Returns the number of variables removed, 0 if none found,
+ *   or FLT_OTEL_RET_ERROR on failure.
+ */
+int flt_otel_vars_unset(struct stream *s, const char *scope, const char *prefix, uint opt, char **err)
+{
+       struct vars    *vars;
+       char            norm_prefix[BUFSIZ];
+       unsigned int    size = 0;
+       int             prefix_len, retval = 0, i;
+
+       OTELC_FUNC("%p, \"%s\", \"%s\", %u, %p:%p", s, OTELC_STR_ARG(scope), OTELC_STR_ARG(prefix), opt, OTELC_DPTR_ARGS(err));
+
+       prefix_len = flt_otel_var_name(prefix, NULL, NULL, 0, norm_prefix, sizeof(norm_prefix), err);
+       if (prefix_len == FLT_OTEL_RET_ERROR)
+               OTELC_RETURN_INT(FLT_OTEL_RET_ERROR);
+
+       vars = flt_otel_vars_get_scope(s, scope);
+       if (vars == NULL)
+               OTELC_RETURN_INT(0);
+
+       /* Lock and iterate all variables, clearing those matching the prefix. */
+       vars_wrlock(vars);
+       for (i = 0; i < VAR_NAME_ROOTS; i++) {
+               struct ceb_node *node = cebu64_imm_first(&(vars->name_root[i]));
+
+               while (node != NULL) {
+                       struct var      *var = container_of(node, struct var, name_node);
+                       struct ceb_node *next = cebu64_imm_next(&(vars->name_root[i]), node);
+
+                       if ((var->name != NULL) &&
+                           (strncmp(var->name, norm_prefix, prefix_len) == 0) &&
+                           (var->name[prefix_len] == '.')) {
+                               OTELC_DBG(NOTICE, "prefix unset '%s'", var->name);
+
+                               size += var_clear(vars, var, 1);
+                               retval++;
+                       }
+
+                       node = next;
+               }
+       }
+       vars_wrunlock(vars);
+
+       if (size > 0)
+               var_accounting_diff(vars, s->sess, s, -(int)size);
+
+       OTELC_RETURN_INT(retval);
+}
+
+
+/***
+ * NAME
+ *   flt_otel_vars_get - context variables to text map via prefix scan
+ *
+ * SYNOPSIS
+ *   struct otelc_text_map *flt_otel_vars_get(struct stream *s, const char *scope, const char *prefix, uint opt, char **err)
+ *
+ * ARGUMENTS
+ *   s      - current stream
+ *   scope  - variable scope
+ *   prefix - variable prefix
+ *   opt    - sample option flags
+ *   err    - indirect pointer to error message string
+ *
+ * DESCRIPTION
+ *   Reads all context variables whose name starts with the normalized
+ *   <prefix> followed by a dot.  Walks the CEB tree of the variable
+ *   store for the given <scope>, denormalizes each matching variable
+ *   name, and adds the key-value pair to the returned text map.
+ *
+ * RETURN VALUE
+ *   Returns a pointer to the populated text map, or NULL if no
+ *   variables are found.
+ */
+struct otelc_text_map *flt_otel_vars_get(struct stream *s, const char *scope, const char *prefix, uint opt, char **err)
+{
+       struct vars           *vars;
+       struct otelc_text_map *retptr = NULL;
+       char                   norm_prefix[BUFSIZ], otel_name[BUFSIZ];
+       int                    prefix_len, i;
+
+       OTELC_FUNC("%p, \"%s\", \"%s\", %u, %p:%p", s, OTELC_STR_ARG(scope), OTELC_STR_ARG(prefix), opt, OTELC_DPTR_ARGS(err));
+
+       prefix_len = flt_otel_var_name(prefix, NULL, NULL, 0, norm_prefix, sizeof(norm_prefix), err);
+       if (prefix_len == FLT_OTEL_RET_ERROR)
+               OTELC_RETURN_PTR(NULL);
+
+       vars = flt_otel_vars_get_scope(s, scope);
+       if (vars == NULL)
+               OTELC_RETURN_PTR(NULL);
+
+       /* Read-lock and collect all variables matching the prefix into a text map. */
+       vars_rdlock(vars);
+       for (i = 0; i < VAR_NAME_ROOTS; i++) {
+               struct ceb_node *node = cebu64_imm_first(&(vars->name_root[i]));
+
+               for ( ; node != NULL; node = cebu64_imm_next(&(vars->name_root[i]), node)) {
+                       struct var *var = container_of(node, struct var, name_node);
+                       const char *key;
+                       int         otel_name_len;
+
+                       if ((var->name == NULL) ||
+                           (strncmp(var->name, norm_prefix, prefix_len) != 0) ||
+                           (var->name[prefix_len] != '.'))
+                               continue;
+
+                       /* Skip the "prefix." part to get the key name. */
+                       key = var->name + prefix_len + 1;
+
+                       otel_name_len = flt_otel_denormalize_name(key, otel_name, sizeof(otel_name), err);
+                       if (otel_name_len == FLT_OTEL_RET_ERROR) {
+                               FLT_OTEL_ERR("failed to reverse variable name, buffer too small");
+
+                               break;
+                       }
+
+                       if ((var->data.type != SMP_T_STR) && (var->data.type != SMP_T_BIN)) {
+                               OTELC_DBG(NOTICE, "skipping '%s', unsupported type %d", var->name, var->data.type);
+
+                               continue;
+                       }
+
+                       OTELC_DBG(NOTICE, "'%s' -> '%.*s'", var->name, (int)b_data(&(var->data.u.str)), b_orig(&(var->data.u.str)));
+
+                       if (retptr == NULL) {
+                               retptr = OTELC_TEXT_MAP_NEW(NULL, 8);
+                               if (retptr == NULL) {
+                                       FLT_OTEL_ERR("failed to create map data");
+
+                                       break;
+                               }
+                       }
+
+                       if (OTELC_TEXT_MAP_ADD(retptr, otel_name, otel_name_len, b_orig(&(var->data.u.str)), b_data(&(var->data.u.str)), OTELC_TEXT_MAP_AUTO) == -1) {
+                               FLT_OTEL_ERR("failed to add map data");
+
+                               otelc_text_map_destroy(&retptr);
+
+                               break;
+                       }
+               }
+       }
+       vars_rdunlock(vars);
+
+       OTELC_TEXT_MAP_DUMP(retptr, "extracted variables");
+
+       if ((retptr != NULL) && (retptr->count == 0)) {
+               OTELC_DBG(NOTICE, "WARNING: no variables found");
+
+               otelc_text_map_destroy(&retptr);
+       }
+
+       OTELC_RETURN_PTR(retptr);
+}
+
+#endif /* USE_OTEL_VARS_NAME */
+
 
 /***
  * NAME
@@ -938,8 +1156,10 @@ int flt_otel_var_set(struct stream *s, const char *scope, const char *prefix, co
 
                retval = var_name_len;
 
+#ifndef USE_OTEL_VARS_NAME
                if (strcmp(scope, FLT_OTEL_VARS_SCOPE) == 0)
                        retval = flt_otel_ctx_set(s, scope, prefix, name, opt, err);
+#endif
        }
 
        OTELC_RETURN_INT(retval);