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.
This commit is contained in:
Miroslav Zagorac 2026-03-02 09:41:57 +01:00 committed by William Lallemand
parent eaa05d2af3
commit bf05a014db
12 changed files with 775 additions and 23 deletions

View file

@ -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] */

View 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)

View 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)

View file

@ -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", " <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. */

View 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);

View 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))));

View 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));
)

View 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)

View 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 {

View 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]);

View 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

View 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
@ -138,6 +145,7 @@
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
@ -166,6 +174,9 @@
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"