]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: otel: added metrics instrument support
authorMiroslav Zagorac <mzagorac@haproxy.com>
Mon, 2 Mar 2026 08:41:57 +0000 (09:41 +0100)
committerWilliam Lallemand <wlallemand@haproxy.com>
Mon, 13 Apr 2026 07:23:26 +0000 (09:23 +0200)
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.

12 files changed:
addons/otel/include/conf.h
addons/otel/include/conf_funcs.h
addons/otel/include/define.h
addons/otel/include/parser.h
addons/otel/include/util.h
addons/otel/src/cli.c
addons/otel/src/conf.c
addons/otel/src/event.c
addons/otel/src/filter.c
addons/otel/src/parser.c
addons/otel/src/util.c
addons/otel/test/sa/otel.cfg

index 08e82b8459e9ec93652906c08f3fae02d57debcc..5441666ee99c2858dd19197b31b86908c8c3d10a 100644 (file)
                         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), \
        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] */
index 4ca6da943b1e78ec1c5a332e6e8a9a66f9c7544c..4459e02bb859c26b65abd8f5a66975fff9f5d735 100644 (file)
@@ -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)
 
index 085869f50f0a29b27654e6feffc59c096a860efc..9e434bb7b57916bf14b1ec7425cdc7389dbf7a42 100644 (file)
@@ -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)
 
index 282ffd8f6c973dc67895c3e0d9f1871c14f38be8..dde98aa472ac77b790efc48e4f8c1999fc8f2286 100644 (file)
 #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"
        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
  * 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",   " <name>")                                            \
-       FLT_OTEL_PARSE_SCOPE_DEF(        SPAN, 0, NONE, 2, 7, "span",         " <name> [<reference>] [<link>] [root]")              \
-       FLT_OTEL_PARSE_SCOPE_DEF(        LINK, 1, NONE, 2, 0,   "link",       " <span> ...")                                        \
-       FLT_OTEL_PARSE_SCOPE_DEF(   ATTRIBUTE, 1, NONE, 3, 0,   "attribute",  " <key> <sample> ...")                                \
-       FLT_OTEL_PARSE_SCOPE_DEF(       EVENT, 1, NONE, 4, 0,   "event",      " <name> <key> <sample> ...")                         \
-       FLT_OTEL_PARSE_SCOPE_DEF(     BAGGAGE, 1,  VAR, 3, 0,   "baggage",    " <key> <sample> ...")                                \
-       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",     " <code> [<sample> ...]")                             \
-       FLT_OTEL_PARSE_SCOPE_DEF(      FINISH, 0, NONE, 2, 0,   "finish",     " <name> ...")                                        \
-       FLT_OTEL_PARSE_SCOPE_DEF(IDLE_TIMEOUT, 0, NONE, 2, 2, "idle-timeout", " <time>")                                            \
-       FLT_OTEL_PARSE_SCOPE_DEF(         ACL, 0, CHAR, 3, 0, "acl",          " <name> <criterion> [flags] [operator] <value> ...") \
+#define FLT_OTEL_PARSE_SCOPE_DEFINES                                                                                                                                           \
+       FLT_OTEL_PARSE_SCOPE_DEF(          ID, 0, CHAR, 2, 2, "otel-scope",   " <name>")                                                                                       \
+       FLT_OTEL_PARSE_SCOPE_DEF(        SPAN, 0, NONE, 2, 7, "span",         " <name> [<reference>] [<link>] [root]")                                                         \
+       FLT_OTEL_PARSE_SCOPE_DEF(        LINK, 1, NONE, 2, 0,   "link",       " <span> ...")                                                                                   \
+       FLT_OTEL_PARSE_SCOPE_DEF(   ATTRIBUTE, 1, NONE, 3, 0,   "attribute",  " <key> <sample> ...")                                                                           \
+       FLT_OTEL_PARSE_SCOPE_DEF(       EVENT, 1, NONE, 4, 0,   "event",      " <name> <key> <sample> ...")                                                                    \
+       FLT_OTEL_PARSE_SCOPE_DEF(     BAGGAGE, 1,  VAR, 3, 0,   "baggage",    " <key> <sample> ...")                                                                           \
+       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",     " <code> [<sample> ...]")                                                                        \
+       FLT_OTEL_PARSE_SCOPE_DEF(      FINISH, 0, NONE, 2, 0,   "finish",     " <name> ...")                                                                                   \
+       FLT_OTEL_PARSE_SCOPE_DEF(  INSTRUMENT, 0, NONE, 3, 0, "instrument",   " { update <name> [<attr> ...] | <type> <name> [<aggr>] [<desc>] [<unit>] <value> [<bounds>] }") \
+       FLT_OTEL_PARSE_SCOPE_DEF(IDLE_TIMEOUT, 0, NONE, 2, 2, "idle-timeout", " <time>")                                                                                       \
+       FLT_OTEL_PARSE_SCOPE_DEF(         ACL, 0, CHAR, 3, 0, "acl",          " <name> <criterion> [flags] [operator] <value> ...")                                            \
        FLT_OTEL_PARSE_SCOPE_DEF(    ON_EVENT, 0, NONE, 2, 0, "otel-event",   " <name> [{ if | unless } <condition>]")
 
 /* Invalid character check modes for identifier validation. */
index 96ee282b1092915df0301a6ec282802985b08736..663e2ba5577aaa029df37f0a782b0f406241511f 100644 (file)
@@ -68,6 +68,9 @@ int         flt_otel_args_count(const char **args);
 /* Concatenate argument array elements into a single string. */
 int         flt_otel_args_concat(const char **args, int idx, int n, char **str);
 
+/* Comparator for qsort: ascending order of doubles with epsilon tolerance. */
+int         flt_otel_qsort_compar_double(const void *a, const void *b);
+
 /* Parse a string to double with range validation. */
 bool        flt_otel_strtod(const char *nptr, double *value, double limit_min, double limit_max, char **err);
 
index 2eda9d4871d7b40c973074d9ad0129f77fb77c18..6bbba2d5525bb16afad9a5d33bc852b64642471a 100644 (file)
@@ -342,9 +342,9 @@ static int flt_otel_cli_parse_rate(char **args, char *payload, struct appctx *ap
  *   Handles the "otel status" CLI command.  Builds a formatted status report
  *   for all OTel filter instances across all proxies.  The report includes
  *   the library version, proxy name, configuration file path, group and scope
- *   counts, disable counts, instrumentation ID, tracer state, rate limit, error
- *   mode, disabled state, logging state, and analyzer bits.  When DEBUG_OTEL is
- *   enabled, the current debug level is also included.
+ *   counts, disable counts, instrumentation ID, tracer and meter state, rate
+ *   limit, error mode, disabled state, logging state, and analyzer bits.  When
+ *   DEBUG_OTEL is enabled, the current debug level is also included.
  *
  * RETURN VALUE
  *   Returns 1, or 0 on memory allocation failure.
@@ -382,6 +382,7 @@ static int flt_otel_cli_parse_status(char **args, char *payload, struct appctx *
                (void)memprintf(&msg, "%s       instrumentation %s\n", msg, conf->instr->id);
                (void)memprintf(&msg, "%s       configuration: %s\n", msg, conf->instr->config);
                (void)memprintf(&msg, "%s       tracer:        %s\n", msg, (conf->instr->tracer != NULL) ? "active" : "not initialized");
+               (void)memprintf(&msg, "%s       meter:         %s\n", msg, (conf->instr->meter != NULL) ? "active" : "not initialized");
                (void)memprintf(&msg, "%s       rate limit:    %.2f %%\n", msg, FLT_OTEL_U32_FLOAT(_HA_ATOMIC_LOAD(&(conf->instr->rate_limit))));
                (void)memprintf(&msg, "%s       hard errors:   %s\n", msg, FLT_OTEL_STR_FLAG_YN(_HA_ATOMIC_LOAD(&(conf->instr->flag_harderr))));
                (void)memprintf(&msg, "%s       disabled:      %s\n", msg, FLT_OTEL_STR_FLAG_YN(_HA_ATOMIC_LOAD(&(conf->instr->flag_disabled))));
index 0914a962d232684f2879d4c754148a014455fc7e..72b9d26f1251907e85b79165be8c9b0fddc158c4 100644 (file)
@@ -486,6 +486,64 @@ FLT_OTEL_CONF_FUNC_FREE(span, id,
 )
 
 
+/***
+ * NAME
+ *   flt_otel_conf_instrument_init - conf_instrument structure allocation
+ *
+ * SYNOPSIS
+ *   struct flt_otel_conf_instrument *flt_otel_conf_instrument_init(const char *id, int line, struct list *head, char **err)
+ *
+ * ARGUMENTS
+ *   id   - identifier string to duplicate
+ *   line - configuration file line number
+ *   head - list to append to (or NULL)
+ *   err  - indirect pointer to error message string
+ *
+ * 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.
+ *
+ * RETURN VALUE
+ *   Returns a pointer to the initialized structure, or NULL on failure.
+ */
+FLT_OTEL_CONF_FUNC_INIT(instrument, id,
+       retptr->idx       = OTELC_METRIC_INSTRUMENT_UNSET;
+       retptr->type      = OTELC_METRIC_INSTRUMENT_UNSET;
+       retptr->aggr_type = OTELC_METRIC_AGGREGATION_UNSET;
+       LIST_INIT(&(retptr->samples));
+)
+
+
+/***
+ * NAME
+ *   flt_otel_conf_instrument_free - conf_instrument structure deallocation
+ *
+ * SYNOPSIS
+ *   void flt_otel_conf_instrument_free(struct flt_otel_conf_instrument **ptr)
+ *
+ * ARGUMENTS
+ *   ptr - a pointer to the address of a structure
+ *
+ * DESCRIPTION
+ *   Deallocates memory used by the flt_otel_conf_instrument structure and its
+ *   contents, then removes it from the list of structures of that type.
+ *
+ * RETURN VALUE
+ *   This function does not return a value.
+ */
+FLT_OTEL_CONF_FUNC_FREE(instrument, id,
+       FLT_OTEL_DBG_CONF_INSTRUMENT("- conf_instrument free ", *ptr);
+
+       OTELC_SFREE((*ptr)->description);
+       OTELC_SFREE((*ptr)->unit);
+       FLT_OTEL_LIST_DESTROY(sample, &((*ptr)->samples));
+       OTELC_SFREE((*ptr)->bounds);
+       otelc_kv_destroy(&((*ptr)->attr), (*ptr)->attr_len);
+)
+
+
 /***
  * NAME
  *   flt_otel_conf_scope_init - conf_scope structure allocation
@@ -513,6 +571,7 @@ FLT_OTEL_CONF_FUNC_INIT(scope, id,
        LIST_INIT(&(retptr->contexts));
        LIST_INIT(&(retptr->spans));
        LIST_INIT(&(retptr->spans_to_finish));
+       LIST_INIT(&(retptr->instruments));
 )
 
 
@@ -548,6 +607,7 @@ FLT_OTEL_CONF_FUNC_FREE(scope, id,
        FLT_OTEL_LIST_DESTROY(context, &((*ptr)->contexts));
        FLT_OTEL_LIST_DESTROY(span, &((*ptr)->spans));
        FLT_OTEL_LIST_DESTROY(str, &((*ptr)->spans_to_finish));
+       FLT_OTEL_LIST_DESTROY(instrument, &((*ptr)->instruments));
 )
 
 
index e2c6ddffa201ac82d25a294ff4b57ee9d212f189..07683fcfa528c2c9b9e59dea99a9752221734cd1 100644 (file)
@@ -9,6 +9,217 @@ const struct flt_otel_event_data flt_otel_event_data[FLT_OTEL_EVENT_MAX] = { FLT
 #undef FLT_OTEL_EVENT_DEF
 
 
+/***
+ * NAME
+ *   flt_otel_scope_run_instrument_record - metric instrument value recorder
+ *
+ * SYNOPSIS
+ *   static int flt_otel_scope_run_instrument_record(struct stream *s, uint dir, struct otelc_meter *meter, struct flt_otel_conf_instrument *instr_ref, struct flt_otel_conf_instrument *instr, char **err)
+ *
+ * ARGUMENTS
+ *   s         - the stream providing the sample context
+ *   dir       - the sample fetch direction (SMP_OPT_DIR_REQ/RES)
+ *   meter     - the OTel meter instance
+ *   instr_ref - the create-form instrument providing samples and meter index
+ *   instr     - the update-form instrument providing per-scope attributes
+ *   err       - indirect pointer to error message string
+ *
+ * DESCRIPTION
+ *   Evaluates sample expressions from a create-form instrument and records
+ *   the resulting value via the <meter> API.  Each expression is evaluated
+ *   with sample_process(), converted to an otelc_value via
+ *   flt_otel_sample_to_value(), and recorded via
+ *   <meter>->update_instrument_kv_n().
+ *
+ * RETURN VALUE
+ *   Returns FLT_OTEL_RET_OK on success, FLT_OTEL_RET_ERROR on failure.
+ */
+static int flt_otel_scope_run_instrument_record(struct stream *s, uint dir, struct otelc_meter *meter, struct flt_otel_conf_instrument *instr_ref, struct flt_otel_conf_instrument *instr, char **err)
+{
+       struct flt_otel_conf_sample      *sample;
+       struct flt_otel_conf_sample_expr *expr;
+       struct sample                     smp;
+       struct otelc_value                value;
+       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));
+
+       /* The samples list always contains exactly one entry. */
+       sample = LIST_NEXT(&(instr_ref->samples), struct flt_otel_conf_sample *, list);
+
+       (void)memset(&smp, 0, sizeof(smp));
+
+       if (sample->lf_used) {
+               /*
+                * Log-format path: evaluate into a temporary buffer and present
+                * the result as a string sample.
+                */
+               smp.data.u.str.area = OTELC_CALLOC(1, global.tune.bufsize);
+               if (smp.data.u.str.area == NULL) {
+                       FLT_OTEL_ERR("out of memory");
+
+                       OTELC_RETURN_INT(FLT_OTEL_RET_ERROR);
+               }
+
+               smp.data.type       = SMP_T_STR;
+               smp.data.u.str.data = build_logline(s, smp.data.u.str.area, global.tune.bufsize, &(sample->lf_expr));
+       } else {
+               /* The expressions list always contains exactly one entry. */
+               expr = LIST_NEXT(&(sample->exprs), struct flt_otel_conf_sample_expr *, list);
+
+               FLT_OTEL_DBG_CONF_SAMPLE_EXPR("sample expression ", expr);
+
+               if (sample_process(s->be, s->sess, s, dir | SMP_OPT_FINAL, expr->expr, &smp) == NULL) {
+                       OTELC_DBG(NOTICE, "WARNING: failed to fetch '%s'", expr->fmt_expr);
+
+                       retval = FLT_OTEL_RET_ERROR;
+               }
+       }
+
+       if (retval == FLT_OTEL_RET_ERROR) {
+               /* Do nothing. */
+       }
+       else if (flt_otel_sample_to_value(sample->key, &(smp.data), &value, err) == FLT_OTEL_RET_ERROR) {
+               if (value.u_type == OTELC_VALUE_DATA)
+                       OTELC_SFREE(value.u.value_data);
+
+               retval = FLT_OTEL_RET_ERROR;
+       }
+       else {
+               OTELC_DBG_VALUE(DEBUG, "value ", &value);
+
+               /*
+                * Metric instruments expect numeric values (INT64 or DOUBLE).
+                * Reject OTELC_VALUE_DATA since the meter cannot interpret
+                * arbitrary string data as a numeric measurement.
+                */
+               if (value.u_type == OTELC_VALUE_DATA) {
+                       OTELC_DBG(NOTICE, "WARNING: non-numeric value type for instrument '%s'", instr_ref->id);
+
+                       if (otelc_value_strtonum(&value, OTELC_VALUE_INT64) == OTELC_RET_ERROR) {
+                               OTELC_SFREE(value.u.value_data);
+
+                               retval = FLT_OTEL_RET_ERROR;
+                       }
+               }
+
+               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)
+                               retval = FLT_OTEL_RET_ERROR;
+       }
+
+       if (sample->lf_used)
+               OTELC_SFREE(smp.data.u.str.area);
+
+       OTELC_RETURN_INT(retval);
+}
+
+
+/***
+ * NAME
+ *   flt_otel_scope_run_instrument - metric instrument processor
+ *
+ * SYNOPSIS
+ *   static int flt_otel_scope_run_instrument(struct stream *s, uint dir, struct flt_otel_conf_scope *scope, struct otelc_meter *meter, char **err)
+ *
+ * ARGUMENTS
+ *   s     - the stream providing the sample context
+ *   dir   - the sample fetch direction (SMP_OPT_DIR_REQ/RES)
+ *   scope - the scope configuration containing the instrument list
+ *   meter - the OTel meter instance
+ *   err   - indirect pointer to error message string
+ *
+ * DESCRIPTION
+ *   Processes all metric instruments configured in <scope>.  Runs in two
+ *   passes: the first pass lazily creates create-form instruments via <meter>
+ *   on first use, using HA_ATOMIC_CAS on the instrument index to guarantee
+ *   thread-safe one-time initialization.  The second pass iterates over
+ *   update-form instruments and records measurements via
+ *   flt_otel_scope_run_instrument_record().  Instruments whose index is still
+ *   negative (UNUSED or PENDING) are skipped, so that a concurrent creation by
+ *   another thread does not cause an invalid <meter> access.
+ *
+ * RETURN VALUE
+ *   Returns FLT_OTEL_RET_OK on success, FLT_OTEL_RET_ERROR on failure.
+ */
+static int flt_otel_scope_run_instrument(struct stream *s, uint dir, struct flt_otel_conf_scope *scope, struct otelc_meter *meter, char **err)
+{
+       struct flt_otel_conf_instrument *conf_instr;
+       int                              retval = FLT_OTEL_RET_OK;
+
+       OTELC_FUNC("%p, %u, %p, %p, %p:%p", s, dir, scope, meter, OTELC_DPTR_ARGS(err));
+
+       list_for_each_entry(conf_instr, &(scope->instruments), list) {
+               if (conf_instr->type == OTELC_METRIC_INSTRUMENT_UPDATE) {
+                       /* Do nothing. */
+               }
+               else if (HA_ATOMIC_LOAD(&(conf_instr->idx)) == OTELC_METRIC_INSTRUMENT_UNSET) {
+                       int64_t expected = OTELC_METRIC_INSTRUMENT_UNSET;
+                       int     rc;
+
+                       OTELC_DBG(DEBUG, "run instrument '%s' -> '%s'", scope->id, conf_instr->id);
+                       FLT_OTEL_DBG_CONF_INSTRUMENT("", conf_instr);
+
+                       /*
+                        * Create form: use this instrument directly.  Lazily
+                        * create the instrument on first use.  Use CAS to
+                        * ensure only one thread performs the creation in a
+                        * multi-threaded environment.
+                        */
+                       if (!HA_ATOMIC_CAS(&(conf_instr->idx), &expected, OTELC_METRIC_INSTRUMENT_PENDING))
+                               continue;
+
+                       /*
+                        * The view must be created before the instrument,
+                        * otherwise bucket boundaries cannot be set.
+                        */
+                       if ((conf_instr->bounds != NULL) && (conf_instr->bounds_num > 0))
+                               if (OTELC_OPS(meter, add_view, conf_instr->id, conf_instr->description, conf_instr->id, conf_instr->unit, conf_instr->type, conf_instr->aggr_type, conf_instr->bounds, conf_instr->bounds_num) == OTELC_RET_ERROR)
+                                       OTELC_DBG(NOTICE, "WARNING: failed to add view for instrument '%s'", conf_instr->id);
+
+                       rc = OTELC_OPS(meter, create_instrument, conf_instr->id, conf_instr->description, conf_instr->unit, conf_instr->type, NULL);
+                       if (rc == OTELC_RET_ERROR) {
+                               OTELC_DBG(NOTICE, "WARNING: failed to create instrument '%s'", conf_instr->id);
+
+                               HA_ATOMIC_STORE(&(conf_instr->idx), OTELC_METRIC_INSTRUMENT_UNSET);
+
+                               retval = FLT_OTEL_RET_ERROR;
+
+                               continue;
+                       } else {
+                               HA_ATOMIC_STORE(&(conf_instr->idx), rc);
+                       }
+               }
+       }
+
+       list_for_each_entry(conf_instr, &(scope->instruments), list)
+               if (conf_instr->type == OTELC_METRIC_INSTRUMENT_UPDATE) {
+                       struct flt_otel_conf_instrument *instr = conf_instr->ref;
+
+                       OTELC_DBG(DEBUG, "run instrument '%s' -> '%s'", scope->id, conf_instr->id);
+                       FLT_OTEL_DBG_CONF_INSTRUMENT("", conf_instr);
+
+                       /*
+                        * Update form: record a measurement using an existing
+                        * create-form instrument.
+                        */
+                       if (instr == NULL) {
+                               OTELC_DBG(NOTICE, "WARNING: invalid reference instrument '%s'", conf_instr->id);
+
+                               retval = FLT_OTEL_RET_ERROR;
+                       }
+                       else if (HA_ATOMIC_LOAD(&(instr->idx)) < 0) {
+                               OTELC_DBG(NOTICE, "WARNING: instrument '%s' not yet created, skipping", instr->id);
+                       }
+                       else if (flt_otel_scope_run_instrument_record(s, dir, meter, instr, conf_instr, err) == FLT_OTEL_RET_ERROR) {
+                               retval = FLT_OTEL_RET_ERROR;
+                       }
+               }
+
+       OTELC_RETURN_INT(retval);
+}
+
+
 /***
  * NAME
  *   flt_otel_scope_run_span - single span execution
@@ -352,6 +563,11 @@ int flt_otel_scope_run(struct stream *s, struct filter *f, struct channel *chn,
                flt_otel_scope_data_free(&data);
        }
 
+       /* Process metric instruments. */
+       if (!LIST_ISEMPTY(&(conf_scope->instruments)))
+               if (flt_otel_scope_run_instrument(s, dir, conf_scope, conf->instr->meter, err) == FLT_OTEL_RET_ERROR)
+                       retval = FLT_OTEL_RET_ERROR;
+
        /* Mark the configured spans for finishing and clean up. */
        list_for_each_entry(span_to_finish, &(conf_scope->spans_to_finish), list)
                if (flt_otel_scope_finish_mark(f->ctx, span_to_finish->str, span_to_finish->str_len) == FLT_OTEL_RET_ERROR)
index de4a19a68f4199c773e6c0aa65c30f86e6919967..7cef5a5636f3773a46e91e57651a33c5b4e91e38 100644 (file)
@@ -194,6 +194,12 @@ static int flt_otel_lib_init(struct flt_otel_conf_instr *instr, char **err)
                        FLT_OTEL_ERR("%s", "failed to initialize OpenTelemetry tracer");
 
                OTELC_RETURN_INT(retval);
+       }
+
+       instr->meter = otelc_meter_create(err);
+       if (instr->meter == NULL) {
+               if (*err == NULL)
+                       FLT_OTEL_ERR("%s", "failed to initialize OpenTelemetry meter");
        } else {
                otelc_ext_init(flt_otel_mem_malloc, flt_otel_mem_free, flt_otel_thread_id);
                otelc_log_set_handler(flt_otel_log_handler_cb, NULL, false);
@@ -408,6 +414,7 @@ static void flt_otel_ops_deinit(struct proxy *p, struct flt_conf *fconf)
 {
        struct flt_otel_conf **conf = (fconf == NULL) ? NULL : (typeof(conf))&(fconf->conf);
        struct otelc_tracer   *otel_tracer = NULL;
+       struct otelc_meter    *otel_meter = NULL;
 #ifdef DEBUG_OTEL
        char                   buffer[BUFSIZ];
        int                    i;
@@ -438,13 +445,15 @@ static void flt_otel_ops_deinit(struct proxy *p, struct flt_conf *fconf)
         * still point to the HAProxy pool allocator; otelc_deinit() resets
         * those callbacks, so it runs last.
         */
-       if ((*conf)->instr != NULL)
+       if ((*conf)->instr != NULL) {
                otel_tracer = (*conf)->instr->tracer;
+               otel_meter  = (*conf)->instr->meter;
+       }
 
        flt_otel_conf_free(conf);
        OTELC_MEMINFO();
        flt_otel_pool_destroy();
-       otelc_deinit(&otel_tracer, NULL, NULL);
+       otelc_deinit(&otel_tracer, &otel_meter, NULL);
 
        OTELC_RETURN();
 }
@@ -723,6 +732,91 @@ static int flt_otel_ops_check(struct proxy *p, struct flt_conf *fconf)
        else if (span_root_cnt > 1)
                FLT_OTEL_ALERT("''%s' : multiple spans are marked as the root span'", conf->id);
 
+       OTELC_DBG(DEBUG, "- defined instruments ----------");
+
+       /*
+        * Validate update-form instruments: for each one, resolve its reference
+        * to the matching create-form instrument definition.
+        *
+        * Validate create-form instruments: check that names are unique across
+        * all scopes.
+        */
+       list_for_each_entry(conf_scope, &(conf->scopes), list) {
+               struct flt_otel_conf_instrument *conf_instr, *instr;
+               struct flt_otel_conf_scope      *scope;
+
+               list_for_each_entry(conf_instr, &(conf_scope->instruments), list) {
+                       if (conf_instr->type == OTELC_METRIC_INSTRUMENT_UPDATE) {
+                               FLT_OTEL_DBG_CONF_INSTRUMENT("  update ", conf_instr);
+
+                               /*
+                                * Search all scopes for a create-form instrument
+                                * whose name matches this update-form instrument.
+                                */
+                               list_for_each_entry(scope, &(conf->scopes), list) {
+                                       list_for_each_entry(instr, &(scope->instruments), list) {
+                                               if ((instr->type != OTELC_METRIC_INSTRUMENT_UPDATE) && (strcmp(instr->id, conf_instr->id) == 0))
+                                                       conf_instr->ref = instr;
+
+                                               if (conf_instr->ref != NULL)
+                                                       break;
+                                       }
+
+                                       if (conf_instr->ref != NULL)
+                                               break;
+                               }
+
+                               if (conf_instr->ref == NULL) {
+                                       FLT_OTEL_ALERT("''%s' : update-form instrument has no matching create-form definition'", conf_instr->id);
+
+                                       retval++;
+                               }
+                       } else {
+                               bool flag_past = false, flag_dup = false;
+
+                               FLT_OTEL_DBG_CONF_INSTRUMENT("  create ", conf_instr);
+
+                               if (LIST_ISEMPTY(&(conf_instr->samples))) {
+                                       FLT_OTEL_ALERT("''%s' : create-form instrument '%s' has no value expression'", conf->id, conf_instr->id);
+
+                                       retval++;
+                               }
+
+                               if ((conf_instr->aggr_type == OTELC_METRIC_AGGREGATION_UNSET) && (conf_instr->type == OTELC_METRIC_INSTRUMENT_HISTOGRAM_UINT64))
+                                       conf_instr->aggr_type = OTELC_METRIC_AGGREGATION_HISTOGRAM;
+
+                               /*
+                                * Checking that create-form instrument names
+                                * are unique across all scopes.  Only compare
+                                * forward to avoid reporting the same pair
+                                * twice.
+                                */
+                               list_for_each_entry(scope, &(conf->scopes), list) {
+                                       list_for_each_entry(instr, &(scope->instruments), list)
+                                               if (instr == conf_instr) {
+                                                       flag_past = true;
+
+                                                       continue;
+                                               }
+                                               else if (!flag_past || (instr->type == OTELC_METRIC_INSTRUMENT_UPDATE)) {
+                                                       continue;
+                                               }
+                                               else if (strcmp(instr->id, conf_instr->id) == 0) {
+                                                       FLT_OTEL_ALERT("''%s' : duplicated create-form instrument '%s''", conf->id, conf_instr->id);
+
+                                                       retval++;
+
+                                                       flag_dup = true;
+                                                       break;
+                                               }
+
+                                       if (flag_dup)
+                                               break;
+                               }
+                       }
+               }
+       }
+
        FLT_OTEL_DBG_LIST(conf, group, "", "defined", _group,
                          FLT_OTEL_DBG_CONF_GROUP("   ", _group);
                          FLT_OTEL_DBG_LIST(_group, ph_scope, "   ", "used", _scope, FLT_OTEL_DBG_CONF_PH("      ", _scope)));
@@ -776,6 +870,12 @@ static int flt_otel_ops_init_per_thread(struct proxy *p, struct flt_conf *fconf)
                if (retval == OTELC_RET_ERROR)
                        FLT_OTEL_ALERT("%s", conf->instr->tracer->err);
 
+               if (retval != OTELC_RET_ERROR) {
+                       retval = OTELC_OPS(conf->instr->meter, start);
+                       if (retval == OTELC_RET_ERROR)
+                               FLT_OTEL_ALERT("%s", conf->instr->meter->err);
+               }
+
                if (retval != FLT_OTEL_RET_ERROR)
                        fconf->flags |= FLT_CFG_FL_HTX;
        } else {
index bcb3b71eb34f1612f2239fe7e79bc5ab9b41ca62..2b3bda7ec808fb570892945d6729de4f4035fca7 100644 (file)
@@ -868,6 +868,284 @@ static struct acl_cond *flt_otel_parse_acl(const char *file, int line, struct pr
 }
 
 
+/***
+ * NAME
+ *   flt_otel_parse_bounds - histogram boundary string parser
+ *
+ * SYNOPSIS
+ *   static int flt_otel_parse_bounds(const char *str, double **bounds, size_t *bounds_num, char **err, const char *err_msg)
+ *
+ * ARGUMENTS
+ *   str        - space-separated numeric boundary string
+ *   bounds     - pointer to the destination boundary array
+ *   bounds_num - pointer to store the number of boundaries
+ *   err        - indirect pointer to error message string
+ *   err_msg    - context label used in error messages
+ *
+ * DESCRIPTION
+ *   Parses a space-separated string of numbers into a dynamically allocated
+ *   array of doubles suitable for the meter add_view API.  The string is
+ *   duplicated internally and tokenized with strtok().  Each token is
+ *   converted with flt_otel_strtod().  The values are sorted internally.
+ *
+ * RETURN VALUE
+ *   Returns ERR_NONE (== 0) in case of success,
+ *   or a combination of ERR_* flags if an error is encountered.
+ */
+static int flt_otel_parse_bounds(const char *str, double **bounds, size_t *bounds_num, char **err, const char *err_msg)
+{
+       char   *buffer, *token, *lasts;
+       size_t  bounds_len = 0, bounds_size = 8;
+       double  value, *ptr;
+       int     retval = ERR_NONE;
+
+       OTELC_FUNC("\"%s\", %p, %p, %p:%p, \"%s\"", OTELC_STR_ARG(str), bounds, bounds_num, OTELC_DPTR_ARGS(err), OTELC_STR_ARG(err_msg));
+
+       buffer  = OTELC_STRDUP(str);
+       *bounds = OTELC_CALLOC(bounds_size, sizeof(**bounds));
+       if ((buffer == NULL) || (*bounds == NULL)) {
+               OTELC_SFREE(buffer);
+               OTELC_SFREE(*bounds);
+
+               FLT_OTEL_PARSE_ERR(err, "'%s' : out of memory", err_msg);
+
+               OTELC_RETURN_INT(retval);
+       }
+
+       /* Tokenize and parse space-separated boundary values. */
+       for (token = strtok_r(buffer, " \t", &lasts); token != NULL; token = strtok_r(NULL, " \t", &lasts)) {
+               if (!flt_otel_strtod(token, &value, 0.0, DBL_MAX, err)) {
+                       retval |= ERR_ABORT | ERR_ALERT;
+
+                       break;
+               }
+               else if (bounds_len >= bounds_size) {
+                       ptr = OTELC_REALLOC(*bounds, (bounds_size + 8) * sizeof(*ptr));
+                       if (ptr == NULL) {
+                               FLT_OTEL_PARSE_ERR(err, "'%s' : out of memory", err_msg);
+
+                               OTELC_SFREE_CLEAR(*bounds);
+
+                               break;
+                       }
+
+                       *bounds      = ptr;
+                       bounds_size += 8;
+               }
+
+               (*bounds)[bounds_len++] = value;
+       }
+
+       /* Sort the bounds and reject duplicates. */
+       if ((*bounds != NULL) && (bounds_len > 1)) {
+               size_t i;
+
+               qsort(*bounds, bounds_len, sizeof(**bounds), flt_otel_qsort_compar_double);
+
+               for (i = 1; i < bounds_len; i++)
+                       if (flt_otel_qsort_compar_double(*bounds + i - 1, *bounds + i) == 0) {
+                               FLT_OTEL_PARSE_ERR(err, "'%s' : duplicate boundary value '%.2f'", err_msg, (*bounds)[i]);
+
+                               OTELC_SFREE_CLEAR(*bounds);
+
+                               break;
+                       }
+       }
+
+       OTELC_SFREE(buffer);
+
+       if (*bounds == NULL) {
+               *bounds_num = 0;
+       }
+       else if (bounds_len == 0) {
+               FLT_OTEL_PARSE_ERR(err, "'%s' : empty bounds", err_msg);
+
+               OTELC_SFREE_CLEAR(*bounds);
+               *bounds_num = 0;
+       }
+       else {
+               *bounds_num = bounds_len;
+       }
+
+       OTELC_RETURN_INT(retval);
+}
+
+
+/***
+ * NAME
+ *   flt_otel_parse_cfg_instrument - instrument keyword parser
+ *
+ * SYNOPSIS
+ *   static int flt_otel_parse_cfg_instrument(const char *file, int line, char **args, const struct flt_otel_parse_data *pdata, char **err)
+ *
+ * ARGUMENTS
+ *   file  - configuration file path
+ *   line  - configuration file line number
+ *   args  - configuration line arguments array
+ *   pdata - keyword metadata (name, usage, argument limits)
+ *   err   - indirect pointer to error message string
+ *
+ * DESCRIPTION
+ *   Parses the "instrument" keyword inside an otel-scope section.  Two forms
+ *   are supported: the "update" form that references an existing instrument by
+ *   name and adds attributes to it, and the "create" form that defines a new
+ *   metric instrument with a type, name, optional aggregation type (preceded by
+ *   the 'aggr' keyword), optional description, optional unit, a single sample
+ *   expression for the value, and optional histogram bucket boundaries
+ *   (preceded by the 'bounds' keyword).  The 'bounds' keyword is only valid for
+ *   histogram instrument types.
+ *
+ * RETURN VALUE
+ *   Returns ERR_NONE (== 0) in case of success,
+ *   or a combination of ERR_* flags if an error is encountered.
+ */
+static int flt_otel_parse_cfg_instrument(const char *file, int line, char **args, const struct flt_otel_parse_data *pdata, char **err)
+{
+#define FLT_OTEL_PARSE_SCOPE_INSTRUMENT_DEF(a,b)   { OTELC_METRIC_INSTRUMENT_##a, b },
+       static const struct {
+               otelc_metric_instrument_t  type;
+               const char                *keyword;
+       } instr_type[] = { FLT_OTEL_PARSE_SCOPE_INSTRUMENT_DEFINES };
+#undef FLT_OTEL_PARSE_SCOPE_INSTRUMENT_DEF
+       struct flt_otel_conf_instrument *instr;
+       int                              i, retval = ERR_NONE;
+
+       OTELC_FUNC("\"%s\", %d, %p, %p, %p:%p", OTELC_STR_ARG(file), line, args, pdata, OTELC_DPTR_ARGS(err));
+
+       /* Look up the instrument type from args[1]. */
+       for (i = 0; i < OTELC_TABLESIZE(instr_type); i++)
+               if (FLT_OTEL_PARSE_KEYWORD(1, instr_type[i].keyword)) {
+                       OTELC_DBG(DEBUG, "instrument type: %d '%s'", instr_type[i].type, instr_type[i].keyword);
+
+                       break;
+               }
+
+       if (i >= OTELC_TABLESIZE(instr_type)) {
+               FLT_OTEL_PARSE_ERR(err, "'%s' : invalid instrument type", args[1]);
+
+               OTELC_RETURN_INT(retval);
+       }
+
+       /*
+        * Only one create and one update instrument per name are allowed.
+        * Pass NULL as head for update instruments to bypass the generic
+        * duplicate check (which would reject the shared name), check for
+        * update duplicates separately, and append to the list manually.
+        */
+       if (instr_type[i].type == OTELC_METRIC_INSTRUMENT_UPDATE) {
+               list_for_each_entry(instr, &(flt_otel_current_scope->instruments), list)
+                       if ((instr->type == OTELC_METRIC_INSTRUMENT_UPDATE) && FLT_OTEL_PARSE_KEYWORD(2, instr->id)) {
+                               FLT_OTEL_ERR("'%s' : already defined", args[2]);
+
+                               OTELC_RETURN_INT(retval);
+                       }
+
+               instr = flt_otel_conf_instrument_init(args[2], line, NULL, err);
+               if (instr != NULL)
+                       LIST_APPEND(&(flt_otel_current_scope->instruments), &(instr->list));
+       } else {
+               instr = flt_otel_conf_instrument_init(args[2], line, &(flt_otel_current_scope->instruments), err);
+       }
+
+       if (instr == NULL) {
+               retval |= ERR_ABORT | ERR_ALERT;
+       }
+       else if (instr_type[i].type == OTELC_METRIC_INSTRUMENT_UPDATE) {
+               bool flag_add_attr = false;
+
+               instr->type = instr_type[i].type;
+
+               /* Update instruments only accept additional attributes. */
+               for (i = 3; !(retval & ERR_CODE) && FLT_OTEL_ARG_ISVALID(i); i++) {
+                       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 if (FLT_OTEL_PARSE_KEYWORD(i, FLT_OTEL_PARSE_INSTRUMENT_ATTR)) {
+                               flag_add_attr = true;
+                       }
+                       else {
+                               FLT_OTEL_PARSE_ERR(err, "'%s' : unknown keyword (use '%s%s')", args[i], pdata->name, pdata->usage);
+                       }
+               }
+
+               if (flag_add_attr && (instr->attr_len == 0))
+                       FLT_OTEL_PARSE_ERR(err, "'%s' : too few arguments (use '%s%s')", args[i], pdata->name, pdata->usage);
+       }
+       else {
+               instr->type = instr_type[i].type;
+
+               /*
+                * Create instruments accept aggr, description, unit, value,
+                * and bounds.
+                */
+               for (i = 3; !(retval & ERR_CODE) && FLT_OTEL_ARG_ISVALID(i); i++) {
+                       if (FLT_OTEL_PARSE_KEYWORD(i, FLT_OTEL_PARSE_INSTRUMENT_AGGR)) {
+                               if (!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 (instr->aggr_type != OTELC_METRIC_AGGREGATION_UNSET)
+                                       FLT_OTEL_PARSE_ERR(err, "'%s' : already set (use '%s%s')", args[i], pdata->name, pdata->usage);
+                               else {
+                                       otelc_metric_aggregation_type_t type = otelc_meter_aggr_parse(args[++i]);
+
+                                       if (type == OTELC_RET_ERROR)
+                                               FLT_OTEL_PARSE_ERR(err, "'%s' : invalid aggregation type", args[i]);
+                                       else
+                                               instr->aggr_type = type;
+                               }
+                       }
+                       else if (FLT_OTEL_PARSE_KEYWORD(i, FLT_OTEL_PARSE_INSTRUMENT_DESC)) {
+                               if (!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 (instr->description == NULL)
+                                       retval = flt_otel_parse_strdup(&(instr->description), NULL, args[++i], err, args[0]);
+                               else
+                                       FLT_OTEL_PARSE_ERR(err, "'%s' : already set (use '%s%s')", args[i], pdata->name, pdata->usage);
+                       }
+                       else if (FLT_OTEL_PARSE_KEYWORD(i, FLT_OTEL_PARSE_INSTRUMENT_UNIT)) {
+                               if (!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 (instr->unit == NULL)
+                                       retval = flt_otel_parse_strdup(&(instr->unit), NULL, args[++i], err, args[0]);
+                               else
+                                       FLT_OTEL_PARSE_ERR(err, "'%s' : already set (use '%s%s')", args[i], pdata->name, pdata->usage);
+                       }
+                       else if (FLT_OTEL_PARSE_KEYWORD(i, FLT_OTEL_PARSE_INSTRUMENT_VALUE)) {
+                               if (!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 (!LIST_ISEMPTY(&(instr->samples)))
+                                       FLT_OTEL_PARSE_ERR(err, "'%s' : already set (use '%s%s')", args[i], pdata->name, pdata->usage);
+                               else {
+                                       retval = flt_otel_parse_cfg_sample(file, line, args, ++i, 1, NULL, &(instr->samples), err);
+
+                                       if (!(retval & ERR_CODE) && FLT_OTEL_ARG_ISVALID(i + 1) && !FLT_OTEL_PARSE_KEYWORD(i + 1, FLT_OTEL_PARSE_INSTRUMENT_AGGR) && !FLT_OTEL_PARSE_KEYWORD(i + 1, FLT_OTEL_PARSE_INSTRUMENT_DESC) && !FLT_OTEL_PARSE_KEYWORD(i + 1, FLT_OTEL_PARSE_INSTRUMENT_UNIT) && !FLT_OTEL_PARSE_KEYWORD(i + 1, FLT_OTEL_PARSE_INSTRUMENT_VALUE) && !FLT_OTEL_PARSE_KEYWORD(i + 1, FLT_OTEL_PARSE_INSTRUMENT_BOUNDS))
+                                               FLT_OTEL_PARSE_ERR(err, "'%s' : only one sample expression allowed per instrument", args[0]);
+                               }
+                       }
+                       else if (FLT_OTEL_PARSE_KEYWORD(i, FLT_OTEL_PARSE_INSTRUMENT_BOUNDS)) {
+                               if (!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 (instr->type != OTELC_METRIC_INSTRUMENT_HISTOGRAM_UINT64)
+                                       FLT_OTEL_PARSE_ERR(err, "'%s' : bounds only valid for hist_int instruments", args[i]);
+                               else if (instr->bounds != NULL)
+                                       FLT_OTEL_PARSE_ERR(err, "'%s' : already set (use '%s%s')", args[i], pdata->name, pdata->usage);
+                               else
+                                       retval = flt_otel_parse_bounds(args[++i], &(instr->bounds), &(instr->bounds_num), err, args[0]);
+                       }
+                       else {
+                               FLT_OTEL_PARSE_ERR(err, "'%s' : invalid argument (use '%s%s')", args[i], pdata->name, pdata->usage);
+                       }
+               }
+       }
+
+       OTELC_RETURN_INT(retval);
+}
+
+
 /***
  * NAME
  *   flt_otel_parse_cfg_scope - otel-scope section parser
@@ -1088,6 +1366,9 @@ static int flt_otel_parse_cfg_scope(const char *file, int line, char **args, int
        else if (pdata->keyword == FLT_OTEL_PARSE_SCOPE_FINISH) {
                retval = flt_otel_parse_cfg_str(file, line, args, &(flt_otel_current_scope->spans_to_finish), &err);
        }
+       else if (pdata->keyword == FLT_OTEL_PARSE_SCOPE_INSTRUMENT) {
+               retval = flt_otel_parse_cfg_instrument(file, line, args, pdata, &err);
+       }
        else if (pdata->keyword == FLT_OTEL_PARSE_SCOPE_ACL) {
                if (FLT_OTEL_PARSE_KEYWORD(1, "or"))
                        FLT_OTEL_PARSE_ERR(&err, "'%s %s ...' : invalid ACL name", args[0], args[1]);
index b1920d8b1cc9afc8a9c59841bd46aa4cbd903745..05108572dfffb6fcfee8628fdf1c0e54ebf53dee 100644 (file)
@@ -347,6 +347,19 @@ int flt_otel_args_concat(const char **args, int idx, int n, char **str)
 }
 
 
+/*
+ * Comparator for qsort: ascending order of doubles.  Values within
+ * FLT_OTEL_DBL_EPSILON of each other are treated as equal.
+ */
+int flt_otel_qsort_compar_double(const void *p1, const void *p2)
+{
+       double a = *(const double *)p1;
+       double b = *(const double *)p2;
+
+       return (fabs(a - b) < FLT_OTEL_DBL_EPSILON) ? 0 : ((a < b) ? -1 : 1);
+}
+
+
 /***
  * NAME
  *   flt_otel_strtod - string to double conversion with range check
index 69f2f32ef865e1866f45ab62d0a81a3bfbd267d0..245e1084c3983a577f7489d434663fa6fb768953 100644 (file)
@@ -57,6 +57,8 @@
             status "error" str("http.status_code: ") status
 
     otel-scope on_stream_start
+        instrument udcnt_int "haproxy.sessions.active" desc "Active sessions"      value int(1)  unit "{session}"
+        instrument gauge_int "haproxy.fe.connections"  desc "Frontend connections" value fe_conn unit "{connection}"
         span "HAProxy session" root
             baggage "haproxy_id" var(sess.otel.uuid)
             event "event_ip" "src" src str(":") src_port
@@ -74,6 +76,8 @@
         idle-timeout 1s
         span "heartbeat" parent "HAProxy session"
             attribute "idle.elapsed" str("idle-check")
+        instrument cnt_int "idle.count" value int(1)
+        instrument update "idle.count"
         otel-event on-idle-timeout
 
     otel-scope client_session_start
@@ -95,6 +99,9 @@
         otel-event on-http-body-request
 
     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"
         span "Frontend HTTP request" parent "HTTP body request" link "HAProxy session"
             attribute "http.method" method
             attribute "http.url" url
         otel-event on-process-sticking-rules-request
 
     otel-scope client_session_end
+        instrument update "haproxy.sessions.active"
         finish "*req*"
         otel-event on-client-session-end
 
         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.fe.connections"
         span "HTTP response" parent "Process store rules response"
             attribute "http.status_code" status
         finish "Process store rules response"