]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: otel: changed instrument attr to use sample expressions
authorMiroslav Zagorac <mzagorac@haproxy.com>
Mon, 6 Apr 2026 04:33:40 +0000 (06:33 +0200)
committerWilliam Lallemand <wlallemand@haproxy.com>
Mon, 13 Apr 2026 07:23:26 +0000 (09:23 +0200)
Replaced the static key-value attribute storage in update-form instruments
with sample-evaluated attributes, matching the log-record attr change.
The 'attr' keyword now accepts a key and a HAProxy sample expression
evaluated at runtime.

The struct (conf.h) changed from otelc_kv/attr_len to a list of
flt_otel_conf_sample entries.  The parser (parser.c) calls
flt_otel_parse_cfg_sample() with n=1 per attr keyword.  At runtime
(event.c) each attribute is evaluated via flt_otel_sample_eval() and
added via flt_otel_sample_add_kv() to a bare flt_otel_scope_data_kv,
which is passed to the meter.

Updated documentation, debug macro and test configurations.

addons/otel/README
addons/otel/README-conf
addons/otel/README-configuration
addons/otel/README-func
addons/otel/README-implementation
addons/otel/include/conf.h
addons/otel/src/conf.c
addons/otel/src/event.c
addons/otel/src/parser.c
addons/otel/test/full/otel.cfg
addons/otel/test/sa/otel.cfg

index c3d8ac17a843e316fd7d2fc3994f613a9a036244..74722e8c012c98b9fe70ec27d142924c777bd5d6 100644 (file)
@@ -628,7 +628,8 @@ instrument { update <name> [<attr>] | <type> <name> [<aggr>] [<desc>] [<unit>] <
 
   To update an existing instrument (previously created in another scope), use
   'update' followed by the name of the instrument.  Optional attributes can be
-  added using the 'attr' keyword followed by key-value pairs.
+  added using the 'attr' keyword followed by a key and a sample expression
+  evaluated at runtime.
 
   Supported instrument types:
     - cnt_int   : counter (uint64)
@@ -656,7 +657,7 @@ instrument { update <name> [<attr>] | <type> <name> [<aggr>] [<desc>] [<unit>] <
     instrument cnt_int  "my_counter" desc "Counter" value int(1)
     instrument hist_int "my_hist" aggr exp_histogram desc "Latency" value lat_ns_tot unit "ns"
     instrument hist_int "my_hist2" desc "Latency" value lat_ns_tot unit "ns" bounds "100 1000 10000 100000"
-    instrument update "my_counter" attr "key1" "val1"
+    instrument update "my_counter" attr "key1" str("val1")
 
   Arguments :
     type   - the instrument type (see list above)
@@ -666,7 +667,7 @@ instrument { update <name> [<attr>] | <type> <name> [<aggr>] [<desc>] [<unit>] <
     unit   - optional unit string for the instrument
     value  - sample expression providing the measurement value
     bounds - optional histogram bucket boundaries (hist_int only)
-    attr   - attribute key-value pairs (update form only)
+    attr   - attribute key and sample expression (update form only)
 
 
 log-record <severity> [id <integer>] [event <name>] [span <span-name>] [attr <key> <sample>] ... <sample> ...
index 2920216ac677df1eaa4288625e932f768f2a163b..f8c1ac8acb580a53d8fcf7bc68c53144f373fe89 100644 (file)
@@ -204,13 +204,13 @@ strings without list linkage.
   samples                Sample expressions for the value.
   bounds                 Histogram bucket boundaries (create only).
   bounds_num             Number of histogram bucket boundaries.
-  attr                   Instrument attributes (update only).
-  attr_len               Number of instrument attributes.
+  attributes             Instrument attributes (update only, flt_otel_conf_sample).
   ref                    Resolved create-form instrument (update only).
 
 Instruments come in two forms: create-form (defines a new metric with type,
 description, unit, and optional histogram bounds) and update-form (references
-an existing instrument via the ref pointer).
+an existing instrument via the ref pointer).  Update-form attributes are stored
+as flt_otel_conf_sample entries and evaluated at runtime.
 
 4.7  flt_otel_conf_log_record
 
index 15a5e3f44ee0e56a53f6d24906960f97474c283d..28f47fd62a001e2a4534112a38d5abc5d2be7fc7 100644 (file)
@@ -320,7 +320,7 @@ Supported keywords:
 
 
   instrument <type> <name> [aggr <aggregation>] [desc <description>] [unit <unit>] value <sample> [bounds <bounds>]
-  instrument update <name> [attr <key> <value> ...]
+  instrument update <name> [attr <key> <sample> ...]
       Create or update a metric instrument.
 
       Supported types:
@@ -358,7 +358,7 @@ Supported keywords:
         instrument cnt_int  "name_cnt_int" desc "Integer Counter" value int(1),add(2) unit "unit"
         instrument hist_int "name_hist" aggr exp_histogram desc "Latency" value lat_ns_tot unit "ns"
         instrument hist_int "name_hist2" desc "Latency" value lat_ns_tot unit "ns" bounds "100 1000 10000"
-        instrument update "name_cnt_int" attr "attr_1_key" "attr_1_value"
+        instrument update "name_cnt_int" attr "attr_1_key" str("attr_1_value")
 
 
   log-record <severity> [id <integer>] [event <name>] [span <span-name>] [attr <key> <sample>] ... <sample> ...
index be47b5e86ff32c167484bea8d748a877f4a12b93..aed3e54446d8f917c0e256d605da964b34d06ef5 100644 (file)
@@ -139,10 +139,11 @@ src/event.c
 Event dispatching, metrics recording and scope/span execution engine.
 
   flt_otel_scope_run_instrument_record
-      Records a measurement for a synchronous metric instrument.  Evaluates the
-      sample expression from the create-form instrument (instr_ref) and submits
-      the value to the meter via update_instrument_kv_n(), using per-scope
-      attributes from the update-form instrument (instr).
+      Records a measurement for a synchronous metric instrument.  Evaluates
+      update-form attributes via flt_otel_sample_eval() and
+      flt_otel_sample_add_kv(), evaluates the sample expression from the
+      create-form instrument (instr_ref), and submits the value to the meter
+      via update_instrument_kv_n().
 
   flt_otel_scope_run_instrument
       Processes all metric instruments for a scope.  Runs in two passes: the
index 4ce66db3f46ab825f35a5c20587337328243e605..5b358c2051af50f0b2307d7c9ac15e4cde5a3b04 100644 (file)
@@ -1052,8 +1052,7 @@ The flt_otel_conf_instrument structure (conf.h) holds:
   samples      List of sample expressions for the instrument value.
   bounds       Histogram bucket boundaries array (create-form only).
   bounds_num   Number of histogram bucket boundaries.
-  attr         Key-value attribute array (update-form only).
-  attr_len     Number of attributes.
+  attributes   List of flt_otel_conf_sample entries (update-form only).
   ref          Pointer to the create-form instrument (update-form only).
 
 18.5.3  Meter Initialization and Startup
index 9bd4c7a49be18629e66e38016f842d317904a71f..6346c51b27ed4391dad6eced816a050961bd6bc6 100644 (file)
                         flt_otel_list_dump(&((p)->ph_scopes)))
 
 #define FLT_OTEL_DBG_CONF_INSTRUMENT(h,p)                                                                                     \
-       OTELC_DBG_STRUCT(DEBUG, h, h FLT_OTEL_CONF_HDR_FMT "%" PRId64 " %d %d '%s' '%s' %s %p %zu %p %zu %p }", (p),          \
+       OTELC_DBG_STRUCT(DEBUG, h, h FLT_OTEL_CONF_HDR_FMT "%" PRId64 " %d %d '%s' '%s' %s %s %p %zu %p }", (p),              \
                         FLT_OTEL_CONF_HDR_ARGS(p, id), (p)->idx, (p)->type, (p)->aggr_type, OTELC_STR_ARG((p)->description), \
-                        OTELC_STR_ARG((p)->unit), flt_otel_list_dump(&((p)->samples)), (p)->attr, (p)->attr_len, (p)->ref,   \
-                        (p)->bounds_num, (p)->bounds)
+                        OTELC_STR_ARG((p)->unit), flt_otel_list_dump(&((p)->samples)), flt_otel_list_dump(&((p)->attributes)), \
+                        (p)->ref, (p)->bounds_num, (p)->bounds)
 
 #define FLT_OTEL_DBG_CONF_LOG_RECORD(h,p)                                                                             \
        OTELC_DBG_STRUCT(DEBUG, h, h FLT_OTEL_CONF_HDR_FMT "%d %" PRId64 " '%s' '%s' %s %s }", (p),                  \
@@ -197,8 +197,7 @@ struct flt_otel_conf_instrument {
        struct list                        samples;     /* Sample expressions for the value. */
        double                            *bounds;      /* Histogram bucket boundaries (create only). */
        size_t                             bounds_num;  /* Number of histogram bucket boundaries. */
-       struct otelc_kv                   *attr;        /* Instrument attributes (update only). */
-       size_t                             attr_len;    /* Number of instrument attributes. */
+       struct list                        attributes;  /* Instrument attributes (update only, flt_otel_conf_sample). */
        struct flt_otel_conf_instrument   *ref;         /* Resolved create-form instrument (update only). */
 };
 
index e86baec28f14736fe7e28317400c6b33feeb32ae..3d1b49eadb67e832310331f5456b1d1cd9935908 100644 (file)
@@ -502,8 +502,9 @@ FLT_OTEL_CONF_FUNC_FREE(span, id,
  * DESCRIPTION
  *   Allocates and initializes a conf_instrument structure.  Sets the instrument
  *   type and meter index to OTELC_METRIC_INSTRUMENT_UNSET and initializes the
- *   samples list.  The <id> string is duplicated and stored as the instrument
- *   name.  If <head> is non-NULL, the structure is appended to the list.
+ *   samples and attributes lists.  The <id> string is duplicated and stored as
+ *   the instrument name.  If <head> is non-NULL, the structure is appended to
+ *   the list.
  *
  * RETURN VALUE
  *   Returns a pointer to the initialized structure, or NULL on failure.
@@ -513,6 +514,7 @@ FLT_OTEL_CONF_FUNC_INIT(instrument, id,
        retptr->type      = OTELC_METRIC_INSTRUMENT_UNSET;
        retptr->aggr_type = OTELC_METRIC_AGGREGATION_UNSET;
        LIST_INIT(&(retptr->samples));
+       LIST_INIT(&(retptr->attributes));
 )
 
 
@@ -540,7 +542,7 @@ FLT_OTEL_CONF_FUNC_FREE(instrument, id,
        OTELC_SFREE((*ptr)->unit);
        FLT_OTEL_LIST_DESTROY(sample, &((*ptr)->samples));
        OTELC_SFREE((*ptr)->bounds);
-       otelc_kv_destroy(&((*ptr)->attr), (*ptr)->attr_len);
+       FLT_OTEL_LIST_DESTROY(sample, &((*ptr)->attributes));
 )
 
 
index e4368b7cac034b19d770e54bb67fd4b4aefcc637..8a6c05dfa4f28f550f20a03469ea33e0ec700913 100644 (file)
@@ -40,10 +40,33 @@ static int flt_otel_scope_run_instrument_record(struct stream *s, uint dir, stru
        struct flt_otel_conf_sample_expr *expr;
        struct sample                     smp;
        struct otelc_value                value;
+       struct flt_otel_scope_data_kv     instr_attr;
        int                               retval = FLT_OTEL_RET_OK;
 
        OTELC_FUNC("%p, %u, %p, %p, %p, %p:%p", s, dir, meter, instr_ref, instr, OTELC_DPTR_ARGS(err));
 
+       /* Evaluate instrument attributes from sample expressions. */
+       (void)memset(&instr_attr, 0, sizeof(instr_attr));
+
+       list_for_each_entry(sample, &(instr->attributes), list) {
+               struct otelc_value attr_value;
+
+               OTELC_DBG(DEBUG, "adding instrument attribute '%s' -> '%s'", sample->key, sample->fmt_string);
+
+               if (flt_otel_sample_eval(s, dir, sample, true, &attr_value, err) == FLT_OTEL_RET_ERROR) {
+                       retval = FLT_OTEL_RET_ERROR;
+
+                       continue;
+               }
+
+               if (flt_otel_sample_add_kv(&instr_attr, sample->key, &attr_value) == FLT_OTEL_RET_ERROR) {
+                       if (attr_value.u_type == OTELC_VALUE_DATA)
+                               OTELC_SFREE(attr_value.u.value_data);
+
+                       retval = FLT_OTEL_RET_ERROR;
+               }
+       }
+
        /* The samples list always contains exactly one entry. */
        sample = LIST_NEXT(&(instr_ref->samples), struct flt_otel_conf_sample *, list);
 
@@ -58,6 +81,8 @@ static int flt_otel_scope_run_instrument_record(struct stream *s, uint dir, stru
                if (smp.data.u.str.area == NULL) {
                        FLT_OTEL_ERR("out of memory");
 
+                       otelc_kv_destroy(&(instr_attr.attr), instr_attr.cnt);
+
                        OTELC_RETURN_INT(FLT_OTEL_RET_ERROR);
                }
 
@@ -104,10 +129,12 @@ static int flt_otel_scope_run_instrument_record(struct stream *s, uint dir, stru
                }
 
                if (retval != FLT_OTEL_RET_ERROR)
-                       if (OTELC_OPS(meter, update_instrument_kv_n, HA_ATOMIC_LOAD(&(instr_ref->idx)), &value, instr->attr, instr->attr_len) == OTELC_RET_ERROR)
+                       if (OTELC_OPS(meter, update_instrument_kv_n, HA_ATOMIC_LOAD(&(instr_ref->idx)), &value, instr_attr.attr, instr_attr.cnt) == OTELC_RET_ERROR)
                                retval = FLT_OTEL_RET_ERROR;
        }
 
+       otelc_kv_destroy(&(instr_attr.attr), instr_attr.cnt);
+
        if (sample->lf_used)
                OTELC_SFREE(smp.data.u.str.area);
 
index 38b81f9e2cc84dc6e4f8e56885b73cf3f1fc42e8..55fe6cb36e351d9c7a4d4765846d753ce433c61f 100644 (file)
@@ -1060,10 +1060,11 @@ static int flt_otel_parse_cfg_instrument(const char *file, int line, char **args
                        if (flag_add_attr) {
                                if (!FLT_OTEL_ARG_ISVALID(i) || !FLT_OTEL_ARG_ISVALID(i + 1))
                                        FLT_OTEL_PARSE_ERR(err, "'%s' : too few arguments (use '%s%s')", args[i], pdata->name, pdata->usage);
-                               else if (otelc_kv_add(&(instr->attr), &(instr->attr_len), args[i], args[i + 1], strlen(args[i + 1])) == OTELC_RET_ERROR)
-                                       FLT_OTEL_PARSE_ERR(err, "'%s' : out of memory", args[0]);
-                               else
-                                       i++;
+                               else {
+                                       retval = flt_otel_parse_cfg_sample(file, line, args, i + 1, 1, NULL, &(instr->attributes), err);
+                                       if (!(retval & ERR_CODE))
+                                               i++;
+                               }
                        }
                        else if (FLT_OTEL_PARSE_KEYWORD(i, FLT_OTEL_PARSE_INSTRUMENT_ATTR)) {
                                flag_add_attr = true;
@@ -1073,7 +1074,7 @@ static int flt_otel_parse_cfg_instrument(const char *file, int line, char **args
                        }
                }
 
-               if (flag_add_attr && (instr->attr_len == 0))
+               if (flag_add_attr && LIST_ISEMPTY(&(instr->attributes)))
                        FLT_OTEL_PARSE_ERR(err, "'%s' : too few arguments (use '%s%s')", args[i], pdata->name, pdata->usage);
        }
        else {
index 86b3a3a547879634e467ec6cd8b4bb5944e46191..921c50791a2f814779fdaabe31cddf104343fafa 100644 (file)
     otel-scope frontend_http_request
         instrument cnt_int  "haproxy.http.requests" desc "HTTP request count"   value int(1)     unit "{request}"
         instrument hist_int "haproxy.http.latency"  desc "HTTP request latency" value lat_ns_tot unit "ns" aggr "histogram" bounds "1000 1000000 1000000000"
-        instrument update "haproxy.http.latency" attr "phase" "request"
+        instrument update "haproxy.http.latency" attr "phase" str("request")
         instrument update "haproxy.tcp.request.fe"
         span "Frontend HTTP request" parent "HTTP body request" link "HAProxy session"
             attribute "http.method" method
         otel-event on-process-store-rules-response
 
     otel-scope http_response
-        instrument update "haproxy.http.requests" attr "phase" "response"
-        instrument update "haproxy.http.latency"  attr "phase" "response"
+        instrument update "haproxy.http.requests" attr "phase" str("response")
+        instrument update "haproxy.http.latency"  attr "phase" str("response")
         instrument update "haproxy.fe.connections"
         span "HTTP response" parent "Process store rules response"
             attribute "http.status_code" status
index 2e1d2733566664d9a0298212101fa95635185ff9..5b213a03755da0312eff64da402f1fbba76000ef 100644 (file)
     otel-scope frontend_http_request
         instrument cnt_int  "haproxy.http.requests" desc "HTTP request count"   value int(1)     unit "{request}"
         instrument hist_int "haproxy.http.latency"  desc "HTTP request latency" value lat_ns_tot unit "ns"
-        instrument update "haproxy.http.latency" attr "phase" "request"
+        instrument update "haproxy.http.latency" attr "phase" str("request")
         span "Frontend HTTP request" parent "HTTP body request" link "HAProxy session"
             attribute "http.method" method
             attribute "http.url" url
         otel-event on-process-store-rules-response
 
     otel-scope http_response
-        instrument update "haproxy.http.requests" attr "phase" "response"
-        instrument update "haproxy.http.latency"  attr "phase" "response"
+        instrument update "haproxy.http.requests" attr "phase" str("response")
+        instrument update "haproxy.http.latency"  attr "phase" str("response")
         instrument update "haproxy.fe.connections"
         span "HTTP response" parent "Process store rules response"
             attribute "http.status_code" status