From 7c66bb5497955d1898192a60b4b20dabdc4cffa5 Mon Sep 17 00:00:00 2001 From: Miroslav Zagorac Date: Mon, 6 Apr 2026 06:33:40 +0200 Subject: [PATCH] MINOR: otel: changed instrument attr to use sample expressions 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 | 7 ++++--- addons/otel/README-conf | 6 +++--- addons/otel/README-configuration | 4 ++-- addons/otel/README-func | 9 +++++---- addons/otel/README-implementation | 3 +-- addons/otel/include/conf.h | 9 ++++----- addons/otel/src/conf.c | 8 +++++--- addons/otel/src/event.c | 29 ++++++++++++++++++++++++++++- addons/otel/src/parser.c | 11 ++++++----- addons/otel/test/full/otel.cfg | 6 +++--- addons/otel/test/sa/otel.cfg | 6 +++--- 11 files changed, 64 insertions(+), 34 deletions(-) diff --git a/addons/otel/README b/addons/otel/README index c3d8ac17a..74722e8c0 100644 --- a/addons/otel/README +++ b/addons/otel/README @@ -628,7 +628,8 @@ instrument { update [] | [] [] [] < 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 [] | [] [] [] < 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 [] | [] [] [] < 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 [id ] [event ] [span ] [attr ] ... ... diff --git a/addons/otel/README-conf b/addons/otel/README-conf index 2920216ac..f8c1ac8ac 100644 --- a/addons/otel/README-conf +++ b/addons/otel/README-conf @@ -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 diff --git a/addons/otel/README-configuration b/addons/otel/README-configuration index 15a5e3f44..28f47fd62 100644 --- a/addons/otel/README-configuration +++ b/addons/otel/README-configuration @@ -320,7 +320,7 @@ Supported keywords: instrument [aggr ] [desc ] [unit ] value [bounds ] - instrument update [attr ...] + instrument update [attr ...] 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 [id ] [event ] [span ] [attr ] ... ... diff --git a/addons/otel/README-func b/addons/otel/README-func index be47b5e86..aed3e5444 100644 --- a/addons/otel/README-func +++ b/addons/otel/README-func @@ -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 diff --git a/addons/otel/README-implementation b/addons/otel/README-implementation index 4ce66db3f..5b358c205 100644 --- a/addons/otel/README-implementation +++ b/addons/otel/README-implementation @@ -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 diff --git a/addons/otel/include/conf.h b/addons/otel/include/conf.h index 9bd4c7a49..6346c51b2 100644 --- a/addons/otel/include/conf.h +++ b/addons/otel/include/conf.h @@ -85,10 +85,10 @@ 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). */ }; diff --git a/addons/otel/src/conf.c b/addons/otel/src/conf.c index e86baec28..3d1b49ead 100644 --- a/addons/otel/src/conf.c +++ b/addons/otel/src/conf.c @@ -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 string is duplicated and stored as the instrument - * name. If is non-NULL, the structure is appended to the list. + * samples and attributes lists. The string is duplicated and stored as + * the instrument name. If 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)); ) diff --git a/addons/otel/src/event.c b/addons/otel/src/event.c index e4368b7ca..8a6c05dfa 100644 --- a/addons/otel/src/event.c +++ b/addons/otel/src/event.c @@ -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); diff --git a/addons/otel/src/parser.c b/addons/otel/src/parser.c index 38b81f9e2..55fe6cb36 100644 --- a/addons/otel/src/parser.c +++ b/addons/otel/src/parser.c @@ -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 { diff --git a/addons/otel/test/full/otel.cfg b/addons/otel/test/full/otel.cfg index 86b3a3a54..921c50791 100644 --- a/addons/otel/test/full/otel.cfg +++ b/addons/otel/test/full/otel.cfg @@ -125,7 +125,7 @@ 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 @@ -235,8 +235,8 @@ 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 diff --git a/addons/otel/test/sa/otel.cfg b/addons/otel/test/sa/otel.cfg index 2e1d27335..5b213a037 100644 --- a/addons/otel/test/sa/otel.cfg +++ b/addons/otel/test/sa/otel.cfg @@ -103,7 +103,7 @@ 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 @@ -177,8 +177,8 @@ 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