From bf05a014db0599ee9a138157d27db18694575c28 Mon Sep 17 00:00:00 2001 From: Miroslav Zagorac Date: Mon, 2 Mar 2026 09:41:57 +0100 Subject: [PATCH] MINOR: otel: added metrics instrument support Added the "instrument" keyword to otel-scope sections for recording metric measurements alongside traces. Introduced flt_otel_conf_instrument holding instrument type, description, unit, sample expressions, and optional key-value attributes. The supported synchronous integer-precision instrument types were counters, histograms, up-down counters, and gauges. Instruments followed a two-form design: a "create" form defined a new instrument with its type and value expression, while an "update" form recorded measurements against an existing instrument with per-scope attributes. Instrument creation was performed lazily at first use with HA_ATOMIC_CAS to guarantee thread-safe one-time initialization. The configuration check phase validated that every update-form had a matching create-form definition and that create-form names were unique across all scopes. The meter lifecycle was integrated into filter init and deinit, starting the meter alongside the tracer and shutting it down during cleanup. --- addons/otel/include/conf.h | 39 ++++- addons/otel/include/conf_funcs.h | 1 + addons/otel/include/define.h | 3 + addons/otel/include/parser.h | 60 +++++-- addons/otel/include/util.h | 3 + addons/otel/src/cli.c | 7 +- addons/otel/src/conf.c | 60 +++++++ addons/otel/src/event.c | 216 ++++++++++++++++++++++++ addons/otel/src/filter.c | 104 +++++++++++- addons/otel/src/parser.c | 281 +++++++++++++++++++++++++++++++ addons/otel/src/util.c | 13 ++ addons/otel/test/sa/otel.cfg | 11 ++ 12 files changed, 775 insertions(+), 23 deletions(-) diff --git a/addons/otel/include/conf.h b/addons/otel/include/conf.h index 08e82b845..5441666ee 100644 --- a/addons/otel/include/conf.h +++ b/addons/otel/include/conf.h @@ -56,10 +56,11 @@ flt_otel_list_dump(&((p)->statuses))) #define FLT_OTEL_DBG_CONF_SCOPE(h,p) \ - OTELC_DBG_STRUCT(DEBUG, h, h FLT_OTEL_CONF_HDR_FMT "%hhu %d %u %s %p %s %s %s }", (p), \ + OTELC_DBG_STRUCT(DEBUG, h, h FLT_OTEL_CONF_HDR_FMT "%hhu %d %u %s %p %s %s %s %s }", (p), \ FLT_OTEL_CONF_HDR_ARGS(p, id), (p)->flag_used, (p)->event, (p)->idle_timeout, \ flt_otel_list_dump(&((p)->acls)), (p)->cond, flt_otel_list_dump(&((p)->contexts)), \ - flt_otel_list_dump(&((p)->spans)), flt_otel_list_dump(&((p)->spans_to_finish))) + flt_otel_list_dump(&((p)->spans)), flt_otel_list_dump(&((p)->spans_to_finish)), \ + flt_otel_list_dump(&((p)->instruments))) #define FLT_OTEL_DBG_CONF_GROUP(h,p) \ OTELC_DBG_STRUCT(DEBUG, h, h FLT_OTEL_CONF_HDR_FMT "%hhu %s }", (p), \ @@ -69,12 +70,18 @@ OTELC_DBG_STRUCT(DEBUG, h, h FLT_OTEL_CONF_HDR_FMT "%p }", (p), FLT_OTEL_CONF_HDR_ARGS(p, id), (p)->ptr) #define FLT_OTEL_DBG_CONF_INSTR(h,p) \ - OTELC_DBG_STRUCT(DEBUG, h, h FLT_OTEL_CONF_HDR_FMT "'%s' %p %u %hhu %hhu 0x%02hhx %p:%s 0x%08x %u %s %s %s }", (p), \ - FLT_OTEL_CONF_HDR_ARGS(p, id), (p)->config, (p)->tracer, (p)->rate_limit, (p)->flag_harderr, \ + OTELC_DBG_STRUCT(DEBUG, h, h FLT_OTEL_CONF_HDR_FMT "'%s' %p %p %u %hhu %hhu 0x%02hhx %p:%s 0x%08x %u %s %s %s }", (p), \ + FLT_OTEL_CONF_HDR_ARGS(p, id), (p)->config, (p)->tracer, (p)->meter, (p)->rate_limit, (p)->flag_harderr, \ (p)->flag_disabled, (p)->logging, &((p)->proxy_log), flt_otel_list_dump(&((p)->proxy_log.loggers)), \ (p)->analyzers, (p)->idle_timeout, flt_otel_list_dump(&((p)->acls)), flt_otel_list_dump(&((p)->ph_groups)), \ 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), \ + 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) + #define FLT_OTEL_DBG_CONF(h,p) \ OTELC_DBG(DEBUG, h "%p:{ %p '%s' '%s' %p %s %s }", (p), \ (p)->proxy, (p)->id, (p)->cfg_file, (p)->instr, \ @@ -112,6 +119,7 @@ struct flt_otel_conf_sample_expr { * flt_otel_conf_span->events (event_name -> OTELC_VALUE_STR(&extra)) * flt_otel_conf_span->baggages * flt_otel_conf_span->statuses (status_code -> extra.u.value_int32) + * flt_otel_conf_instrument->samples */ struct flt_otel_conf_sample { FLT_OTEL_CONF_HDR(key); /* The list containing sample names. */ @@ -161,6 +169,25 @@ struct flt_otel_conf_span { struct list statuses; /* Span status code and description (only one per list). */ }; +/* + * Metric instrument configuration within a scope. + * flt_otel_conf_scope->instruments + */ +struct flt_otel_conf_instrument { + FLT_OTEL_CONF_HDR(id); /* The name of the instrument. */ + int64_t idx; /* Meter instrument index (-1 if not yet created). */ + otelc_metric_instrument_t type; /* Instrument type (or UPDATE). */ + otelc_metric_aggregation_type_t aggr_type; /* Aggregation type for the view (create only). */ + char *description; /* Instrument description (create only). */ + char *unit; /* Instrument unit (create only). */ + 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 flt_otel_conf_instrument *ref; /* Resolved create-form instrument (update only). */ +}; + /* Configuration for a single event scope. */ struct flt_otel_conf_scope { FLT_OTEL_CONF_HDR(id); /* The scope name. */ @@ -172,6 +199,7 @@ struct flt_otel_conf_scope { struct list contexts; /* Declared contexts. */ struct list spans; /* Declared spans. */ struct list spans_to_finish; /* The list of spans scheduled for finishing. */ + struct list instruments; /* The list of metric instruments. */ }; /* Configuration for a named group of scopes. */ @@ -189,11 +217,12 @@ struct flt_otel_conf_ph { #define flt_otel_conf_ph_group flt_otel_conf_ph #define flt_otel_conf_ph_scope flt_otel_conf_ph -/* Top-level OTel instrumentation settings (tracer, options). */ +/* Top-level OTel instrumentation settings (tracer, meter, options). */ struct flt_otel_conf_instr { FLT_OTEL_CONF_HDR(id); /* The OpenTelemetry instrumentation name. */ char *config; /* The OpenTelemetry configuration file name. */ struct otelc_tracer *tracer; /* The OpenTelemetry tracer handle. */ + struct otelc_meter *meter; /* The OpenTelemetry meter handle. */ uint32_t rate_limit; /* [0 2^32-1] <-> [0.0 100.0] */ bool flag_harderr; /* [0 1] */ bool flag_disabled; /* [0 1] */ diff --git a/addons/otel/include/conf_funcs.h b/addons/otel/include/conf_funcs.h index 4ca6da943..4459e02bb 100644 --- a/addons/otel/include/conf_funcs.h +++ b/addons/otel/include/conf_funcs.h @@ -105,6 +105,7 @@ FLT_OTEL_CONF_FUNC_DECL(link) FLT_OTEL_CONF_FUNC_DECL(context) FLT_OTEL_CONF_FUNC_DECL(span) FLT_OTEL_CONF_FUNC_DECL(scope) +FLT_OTEL_CONF_FUNC_DECL(instrument) FLT_OTEL_CONF_FUNC_DECL(group) FLT_OTEL_CONF_FUNC_DECL(instr) diff --git a/addons/otel/include/define.h b/addons/otel/include/define.h index 085869f50..9e434bb7b 100644 --- a/addons/otel/include/define.h +++ b/addons/otel/include/define.h @@ -28,6 +28,9 @@ /* Compare a runtime string against a compile-time string literal. */ #define FLT_OTEL_STR_CMP(S,s) ((s##_len == FLT_OTEL_STR_SIZE(S)) && (memcmp((s), FLT_OTEL_STR_ADDRSIZE(S)) == 0)) +/* Tolerance for double comparison in flt_otel_qsort_compar_double(). */ +#define FLT_OTEL_DBL_EPSILON 1e-9 + /* Execute a statement exactly once across all invocations. */ #define FLT_OTEL_RUN_ONCE(f) do { static bool _f = 1; if (_f) { _f = 0; { f; } } } while (0) diff --git a/addons/otel/include/parser.h b/addons/otel/include/parser.h index 282ffd8f6..dde98aa47 100644 --- a/addons/otel/include/parser.h +++ b/addons/otel/include/parser.h @@ -20,6 +20,12 @@ #define FLT_OTEL_PARSE_SPAN_ROOT "root" #define FLT_OTEL_PARSE_SPAN_PARENT "parent" #define FLT_OTEL_PARSE_SPAN_LINK "link" +#define FLT_OTEL_PARSE_INSTRUMENT_DESC "desc" +#define FLT_OTEL_PARSE_INSTRUMENT_VALUE "value" +#define FLT_OTEL_PARSE_INSTRUMENT_ATTR "attr" +#define FLT_OTEL_PARSE_INSTRUMENT_UNIT "unit" +#define FLT_OTEL_PARSE_INSTRUMENT_BOUNDS "bounds" +#define FLT_OTEL_PARSE_INSTRUMENT_AGGR "aggr" #define FLT_OTEL_PARSE_CTX_AUTONAME "-" #define FLT_OTEL_PARSE_CTX_IGNORE_NAME '-' #define FLT_OTEL_PARSE_CTX_USE_HEADERS "use-headers" @@ -65,6 +71,33 @@ FLT_OTEL_PARSE_SCOPE_STATUS_DEF( OK, "ok" ) \ FLT_OTEL_PARSE_SCOPE_STATUS_DEF( ERROR, "error" ) +/* Sentinel: instrument has not been created yet. */ +#define OTELC_METRIC_INSTRUMENT_UNSET -1 + +/* Sentinel: instrument creation is in progress by another thread. */ +#define OTELC_METRIC_INSTRUMENT_PENDING -2 + +/* Sentinel: update-form instrument (re-evaluates an existing one). */ +#define OTELC_METRIC_INSTRUMENT_UPDATE 0xff + +#define OTELC_METRIC_AGGREGATION_UNSET -1 + +/* + * Observable (asynchronous) instruments are not supported. The OTel SDK + * invokes their callbacks from an external background thread that is not a + * HAProxy thread. HAProxy sample fetches rely on internal per-thread-group + * state and return incorrect results when called from a non-HAProxy thread. + * + * Double-precision instruments are not supported because HAProxy sample fetches + * do not return double values. + */ +#define FLT_OTEL_PARSE_SCOPE_INSTRUMENT_DEFINES \ + FLT_OTEL_PARSE_SCOPE_INSTRUMENT_DEF(UPDATE, "update" ) \ + FLT_OTEL_PARSE_SCOPE_INSTRUMENT_DEF(COUNTER_UINT64, "cnt_int" ) \ + FLT_OTEL_PARSE_SCOPE_INSTRUMENT_DEF(HISTOGRAM_UINT64, "hist_int" ) \ + FLT_OTEL_PARSE_SCOPE_INSTRUMENT_DEF(UDCOUNTER_INT64, "udcnt_int") \ + FLT_OTEL_PARSE_SCOPE_INSTRUMENT_DEF(GAUGE_INT64, "gauge_int") + /* * In case the possibility of working with OpenTelemetry context via HAProxy * variables is not used, args_max member of the structure flt_otel_parse_data @@ -72,19 +105,20 @@ * because in this case the 'use-vars' argument cannot be entered anyway, * so I will not complicate it here with additional definitions. */ -#define FLT_OTEL_PARSE_SCOPE_DEFINES \ - FLT_OTEL_PARSE_SCOPE_DEF( ID, 0, CHAR, 2, 2, "otel-scope", " ") \ - FLT_OTEL_PARSE_SCOPE_DEF( SPAN, 0, NONE, 2, 7, "span", " [] [] [root]") \ - FLT_OTEL_PARSE_SCOPE_DEF( LINK, 1, NONE, 2, 0, "link", " ...") \ - FLT_OTEL_PARSE_SCOPE_DEF( ATTRIBUTE, 1, NONE, 3, 0, "attribute", " ...") \ - FLT_OTEL_PARSE_SCOPE_DEF( EVENT, 1, NONE, 4, 0, "event", " ...") \ - FLT_OTEL_PARSE_SCOPE_DEF( BAGGAGE, 1, VAR, 3, 0, "baggage", " ...") \ - FLT_OTEL_PARSE_SCOPE_DEF( INJECT, 1, CTX, 2, 4, "inject", FLT_OTEL_PARSE_SCOPE_INJECT_HELP) \ - FLT_OTEL_PARSE_SCOPE_DEF( EXTRACT, 0, CTX, 2, 3, "extract", FLT_OTEL_PARSE_SCOPE_EXTRACT_HELP) \ - FLT_OTEL_PARSE_SCOPE_DEF( STATUS, 1, NONE, 2, 0, "status", " [ ...]") \ - FLT_OTEL_PARSE_SCOPE_DEF( FINISH, 0, NONE, 2, 0, "finish", " ...") \ - FLT_OTEL_PARSE_SCOPE_DEF(IDLE_TIMEOUT, 0, NONE, 2, 2, "idle-timeout", "