diff --git a/addons/otel/include/config.h b/addons/otel/include/config.h index cc9340faa..9042ae827 100644 --- a/addons/otel/include/config.h +++ b/addons/otel/include/config.h @@ -19,6 +19,9 @@ #define FLT_OTEL_ID_MAXLEN 64 /* Maximum identifier length. */ #define FLT_OTEL_DEBUG_LEVEL 0b11101111111 /* Default debug bitmask. */ +#define FLT_OTEL_ATTR_INIT_SIZE 8 /* Initial attribute array capacity. */ +#define FLT_OTEL_ATTR_INC_SIZE 4 /* Attribute array growth increment. */ + #endif /* _OTEL_CONFIG_H_ */ /* diff --git a/addons/otel/include/define.h b/addons/otel/include/define.h index 4ca3c28ae..a4a8130b6 100644 --- a/addons/otel/include/define.h +++ b/addons/otel/include/define.h @@ -15,6 +15,12 @@ /* Compile-time string length excluding the null terminator. */ #define FLT_OTEL_STR_SIZE(a) (sizeof(a) - 1) +/* Expand to address and length pair for a string literal. */ +#define FLT_OTEL_STR_ADDRSIZE(a) (a), FLT_OTEL_STR_SIZE(a) + +/* 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)) + /* Execute a statement exactly once across all invocations. */ #define FLT_OTEL_RUN_ONCE(f) do { static bool _f = 1; if (_f) { _f = 0; { f; } } } while (0) diff --git a/addons/otel/include/event.h b/addons/otel/include/event.h index 3ad528f7a..405195603 100644 --- a/addons/otel/include/event.h +++ b/addons/otel/include/event.h @@ -108,6 +108,14 @@ enum FLT_OTEL_EVENT_enum { #undef FLT_OTEL_EVENT_DEF }; +/* Sample data types associated with a scope event. */ +enum FLT_OTEL_EVENT_SAMPLE_enum { + FLT_OTEL_EVENT_SAMPLE_ATTRIBUTE = 0, + FLT_OTEL_EVENT_SAMPLE_EVENT, + FLT_OTEL_EVENT_SAMPLE_BAGGAGE, + FLT_OTEL_EVENT_SAMPLE_STATUS, +}; + /* Per-event metadata mapping analyzer bits to filter event names. */ struct flt_otel_event_data { uint an_bit; /* Used channel analyser. */ @@ -119,11 +127,16 @@ struct flt_otel_event_data { const char *name; /* Filter event name. */ }; +struct flt_otel_conf_scope; + /* Per-event metadata table indexed by FLT_OTEL_EVENT_* constants. */ extern const struct flt_otel_event_data flt_otel_event_data[FLT_OTEL_EVENT_MAX]; +/* Execute a single scope: create spans, record instruments, evaluate samples. */ +int flt_otel_scope_run(struct stream *s, struct filter *f, struct channel *chn, struct flt_otel_conf_scope *conf_scope, const struct timespec *ts_steady, const struct timespec *ts_system, uint dir, char **err); + /* Run all scopes matching a filter event on the given stream and channel. */ int flt_otel_event_run(struct stream *s, struct filter *f, struct channel *chn, int event, char **err); diff --git a/addons/otel/include/scope.h b/addons/otel/include/scope.h index 30e398e13..7570e6222 100644 --- a/addons/otel/include/scope.h +++ b/addons/otel/include/scope.h @@ -143,6 +143,12 @@ void flt_otel_scope_data_init(struct flt_otel_scope_ /* Free all scope data contents. */ void flt_otel_scope_data_free(struct flt_otel_scope_data *ptr); +/* Mark a span for finishing by name in the runtime context. */ +int flt_otel_scope_finish_mark(const struct flt_otel_runtime_context *rt_ctx, const char *id, size_t id_len); + +/* End all spans that have been marked for finishing. */ +void flt_otel_scope_finish_marked(const struct flt_otel_runtime_context *rt_ctx, const struct timespec *ts_finish); + /* Free scope spans and contexts no longer needed by a channel. */ void flt_otel_scope_free_unused(struct flt_otel_runtime_context *rt_ctx, struct channel *chn); diff --git a/addons/otel/include/util.h b/addons/otel/include/util.h index 5db8cb874..aba8722f0 100644 --- a/addons/otel/include/util.h +++ b/addons/otel/include/util.h @@ -58,6 +58,12 @@ bool flt_otel_strtoll(const char *nptr, int64_t *value, int64_t limit_min /* Convert sample data to a string representation. */ int flt_otel_sample_to_str(const struct sample_data *data, char *value, size_t size, char **err); +/* Convert sample data to an OTel value. */ +int flt_otel_sample_to_value(const char *key, const struct sample_data *data, struct otelc_value *value, char **err); + +/* Evaluate a sample expression and add the result to scope data. */ +int flt_otel_sample_add(struct stream *s, uint dir, struct flt_otel_conf_sample *sample, struct flt_otel_scope_data *data, int type, char **err); + #endif /* _OTEL_UTIL_H_ */ /* diff --git a/addons/otel/src/event.c b/addons/otel/src/event.c index 503a94702..b6666541d 100644 --- a/addons/otel/src/event.c +++ b/addons/otel/src/event.c @@ -9,6 +9,78 @@ 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_span - single span execution + * + * SYNOPSIS + * static int flt_otel_scope_run_span(struct stream *s, struct filter *f, struct channel *chn, uint dir, struct flt_otel_scope_span *span, struct flt_otel_scope_data *data, const struct flt_otel_conf_span *conf_span, const struct timespec *ts_steady, const struct timespec *ts_system, char **err) + * + * ARGUMENTS + * s - the stream being processed + * f - the filter instance + * chn - the channel used for HTTP header injection + * dir - the sample fetch direction (SMP_OPT_DIR_REQ/RES) + * span - the runtime scope span to execute + * data - the evaluated scope data (attributes, events, links, status) + * conf_span - the span configuration + * ts_steady - the monotonic timestamp for span creation + * ts_system - the wall-clock timestamp for span events + * err - indirect pointer to error message string + * + * DESCRIPTION + * Executes a single span: creates the OTel span on first call via the tracer, + * adds links, baggage, attributes, events and status from , then + * injects the span context into HTTP headers if configured in . + * + * RETURN VALUE + * Returns FLT_OTEL_RET_OK on success, FLT_OTEL_RET_ERROR on failure. + */ +static int flt_otel_scope_run_span(struct stream *s, struct filter *f, struct channel *chn, uint dir, struct flt_otel_scope_span *span, struct flt_otel_scope_data *data, const struct flt_otel_conf_span *conf_span, const struct timespec *ts_steady, const struct timespec *ts_system, char **err) +{ + struct flt_otel_conf *conf = FLT_OTEL_CONF(f); + int retval = FLT_OTEL_RET_OK; + + OTELC_FUNC("%p, %p, %p, %u, %p, %p, %p, %p, %p, %p:%p", s, f, chn, dir, span, data, conf_span, ts_steady, ts_system, OTELC_DPTR_ARGS(err)); + + if (span == NULL) + OTELC_RETURN_INT(retval); + + /* Create the OTel span on first invocation. */ + if (span->span == NULL) { + span->span = OTELC_OPS(conf->instr->tracer, start_span_with_options, span->id, span->ref_span, span->ref_ctx, ts_steady, ts_system, OTELC_SPAN_KIND_SERVER, NULL, 0); + if (span->span == NULL) + OTELC_RETURN_INT(FLT_OTEL_RET_ERROR); + } + + /* Set baggage key-value pairs on the span. */ + if (data->baggage.attr != NULL) + if (OTELC_OPS(span->span, set_baggage_kv_n, data->baggage.attr, data->baggage.cnt) == -1) + retval = FLT_OTEL_RET_ERROR; + + /* Set span attributes. */ + if (data->attributes.attr != NULL) + if (OTELC_OPS(span->span, set_attribute_kv_n, data->attributes.attr, data->attributes.cnt) == -1) + retval = FLT_OTEL_RET_ERROR; + + /* Add span events in reverse order. */ + if (!LIST_ISEMPTY(&(data->events))) { + struct flt_otel_scope_data_event *event; + + list_for_each_entry_rev(event, &(data->events), list) + if (OTELC_OPS(span->span, add_event_kv_n, event->name, ts_system, event->attr, event->cnt) == -1) + retval = FLT_OTEL_RET_ERROR; + } + + /* Set span status code and description. */ + if (data->status.description != NULL) + if (OTELC_OPS(span->span, set_status, data->status.code, data->status.description) == -1) + retval = FLT_OTEL_RET_ERROR; + + OTELC_RETURN_INT(retval); +} + + /*** * NAME * flt_otel_scope_run - scope execution engine @@ -36,15 +108,134 @@ const struct flt_otel_event_data flt_otel_event_data[FLT_OTEL_EVENT_MAX] = { FLT * RETURN VALUE * Returns FLT_OTEL_RET_OK on success, FLT_OTEL_RET_ERROR on failure. */ -static int flt_otel_scope_run(struct stream *s, struct filter *f, struct channel *chn, struct flt_otel_conf_scope *conf_scope, const struct timespec *ts_steady, const struct timespec *ts_system, uint dir, char **err) +int flt_otel_scope_run(struct stream *s, struct filter *f, struct channel *chn, struct flt_otel_conf_scope *conf_scope, const struct timespec *ts_steady, const struct timespec *ts_system, uint dir, char **err) { +#ifdef FLT_OTEL_USE_COUNTERS + struct flt_otel_conf *conf = FLT_OTEL_CONF(f); +#endif + struct flt_otel_conf_span *conf_span; + struct flt_otel_conf_str *span_to_finish; + struct timespec ts_now_steady, ts_now_system; + int retval = FLT_OTEL_RET_OK; + OTELC_FUNC("%p, %p, %p, %p, %p, %p, %u, %p:%p", s, f, chn, conf_scope, ts_steady, ts_system, dir, OTELC_DPTR_ARGS(err)); OTELC_DBG(DEBUG, "channel: %s, mode: %s (%s)", flt_otel_chn_label(chn), flt_otel_pr_mode(s), flt_otel_stream_pos(s)); OTELC_DBG(DEBUG, "run scope '%s' %d", conf_scope->id, conf_scope->event); FLT_OTEL_DBG_CONF_SCOPE("run scope ", conf_scope); - OTELC_RETURN_INT(FLT_OTEL_RET_OK); + if (ts_steady == NULL) { + (void)clock_gettime(CLOCK_MONOTONIC, &ts_now_steady); + + ts_steady = &ts_now_steady; + } + if (ts_system == NULL) { + (void)clock_gettime(CLOCK_REALTIME, &ts_now_system); + + ts_system = &ts_now_system; + } + + /* Evaluate the scope's ACL condition; skip this scope on mismatch. */ + if (conf_scope->cond != NULL) { + enum acl_test_res res; + int rc; + + res = acl_exec_cond(conf_scope->cond, s->be, s->sess, s, dir | SMP_OPT_FINAL); + rc = acl_pass(res); + if (conf_scope->cond->pol == ACL_COND_UNLESS) + rc = !rc; + + OTELC_DBG(DEBUG, "the ACL rule %s", rc ? "matches" : "does not match"); + + /* + * If the rule does not match, the current scope is skipped. + * + * If it is a root span, further processing of the session is + * disabled. As soon as the first span is encountered which + * is marked as root, further search is interrupted. + */ + if (rc == 0) { + list_for_each_entry(conf_span, &(conf_scope->spans), list) + if (conf_span->flag_root) { + OTELC_DBG(LOG, "session disabled"); + + FLT_OTEL_RT_CTX(f->ctx)->flag_disabled = 1; + +#ifdef FLT_OTEL_USE_COUNTERS + _HA_ATOMIC_ADD(conf->cnt.disabled + 0, 1); +#endif + + break; + } + + OTELC_RETURN_INT(retval); + } + } + + /* Process configured spans: resolve links and collect samples. */ + list_for_each_entry(conf_span, &(conf_scope->spans), list) { + struct flt_otel_scope_data data; + struct flt_otel_scope_span *span; + struct flt_otel_conf_sample *sample; + + OTELC_DBG(DEBUG, "run span '%s' -> '%s'", conf_scope->id, conf_span->id); + FLT_OTEL_DBG_CONF_SPAN("run span ", conf_span); + + flt_otel_scope_data_init(&data); + + span = flt_otel_scope_span_init(f->ctx, conf_span->id, conf_span->id_len, conf_span->ref_id, conf_span->ref_id_len, dir, err); + if (span == NULL) + retval = FLT_OTEL_RET_ERROR; + + list_for_each_entry(sample, &(conf_span->attributes), list) { + OTELC_DBG(DEBUG, "adding attribute '%s' -> '%s'", sample->key, sample->fmt_string); + + if (flt_otel_sample_add(s, dir, sample, &data, FLT_OTEL_EVENT_SAMPLE_ATTRIBUTE, err) == FLT_OTEL_RET_ERROR) + retval = FLT_OTEL_RET_ERROR; + } + + list_for_each_entry(sample, &(conf_span->events), list) { + OTELC_DBG(DEBUG, "adding event '%s' -> '%s'", sample->key, sample->fmt_string); + + if (flt_otel_sample_add(s, dir, sample, &data, FLT_OTEL_EVENT_SAMPLE_EVENT, err) == FLT_OTEL_RET_ERROR) + retval = FLT_OTEL_RET_ERROR; + } + + list_for_each_entry(sample, &(conf_span->baggages), list) { + OTELC_DBG(DEBUG, "adding baggage '%s' -> '%s'", sample->key, sample->fmt_string); + + if (flt_otel_sample_add(s, dir, sample, &data, FLT_OTEL_EVENT_SAMPLE_BAGGAGE, err) == FLT_OTEL_RET_ERROR) + retval = FLT_OTEL_RET_ERROR; + } + + /* + * Regardless of the use of the list, only one status per event + * is allowed. + */ + list_for_each_entry(sample, &(conf_span->statuses), list) { + OTELC_DBG(DEBUG, "adding status '%s' -> '%s'", sample->key, sample->fmt_string); + + if (flt_otel_sample_add(s, dir, sample, &data, FLT_OTEL_EVENT_SAMPLE_STATUS, err) == FLT_OTEL_RET_ERROR) + retval = FLT_OTEL_RET_ERROR; + } + + /* Attempt to run the span regardless of earlier errors. */ + if (span != NULL) + if (flt_otel_scope_run_span(s, f, chn, dir, span, &data, conf_span, ts_steady, ts_system, err) == FLT_OTEL_RET_ERROR) + retval = FLT_OTEL_RET_ERROR; + + flt_otel_scope_data_free(&data); + } + + /* 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) + retval = FLT_OTEL_RET_ERROR; + + flt_otel_scope_finish_marked(f->ctx, ts_steady); + flt_otel_scope_free_unused(f->ctx, chn); + + OTELC_RETURN_INT(retval); } diff --git a/addons/otel/src/scope.c b/addons/otel/src/scope.c index b0594d59e..303fc2a07 100644 --- a/addons/otel/src/scope.c +++ b/addons/otel/src/scope.c @@ -484,6 +484,162 @@ void flt_otel_scope_data_free(struct flt_otel_scope_data *ptr) } +/*** + * NAME + * flt_otel_scope_finish_mark - mark spans and contexts for finishing + * + * SYNOPSIS + * int flt_otel_scope_finish_mark(const struct flt_otel_runtime_context *rt_ctx, const char *id, size_t id_len) + * + * ARGUMENTS + * rt_ctx - the runtime context containing spans and contexts + * id - the target name, or a wildcard ("*", "*req*", "*res*") + * id_len - length of the string + * + * DESCRIPTION + * Marks spans and contexts for finishing. The argument supports + * wildcards: "*" marks all spans and contexts, "*req*" marks the request + * channel only, "*res*" marks the response channel only. Otherwise, a named + * span or context is looked up by exact match. + * + * RETURN VALUE + * Returns the number of spans and contexts that were marked. + */ +int flt_otel_scope_finish_mark(const struct flt_otel_runtime_context *rt_ctx, const char *id, size_t id_len) +{ + struct flt_otel_scope_span *span; + struct flt_otel_scope_context *ctx; + int span_cnt = 0, ctx_cnt = 0, retval; + + OTELC_FUNC("%p, \"%s\", %zu", rt_ctx, OTELC_STR_ARG(id), id_len); + + /* Handle wildcard finish marks: all, request-only, response-only. */ + if (FLT_OTEL_STR_CMP(FLT_OTEL_SCOPE_SPAN_FINISH_ALL, id)) { + list_for_each_entry(span, &(rt_ctx->spans), list) { + span->flag_finish = 1; + span_cnt++; + } + + list_for_each_entry(ctx, &(rt_ctx->contexts), list) { + ctx->flag_finish = 1; + ctx_cnt++; + } + + OTELC_DBG(NOTICE, "marked %d span(s), %d context(s)", span_cnt, ctx_cnt); + } + else if (FLT_OTEL_STR_CMP(FLT_OTEL_SCOPE_SPAN_FINISH_REQ, id)) { + list_for_each_entry(span, &(rt_ctx->spans), list) + if (span->smp_opt_dir == SMP_OPT_DIR_REQ) { + span->flag_finish = 1; + span_cnt++; + } + + list_for_each_entry(ctx, &(rt_ctx->contexts), list) + if (ctx->smp_opt_dir == SMP_OPT_DIR_REQ) { + ctx->flag_finish = 1; + ctx_cnt++; + } + + OTELC_DBG(NOTICE, "marked REQuest channel %d span(s), %d context(s)", span_cnt, ctx_cnt); + } + else if (FLT_OTEL_STR_CMP(FLT_OTEL_SCOPE_SPAN_FINISH_RES, id)) { + list_for_each_entry(span, &(rt_ctx->spans), list) + if (span->smp_opt_dir == SMP_OPT_DIR_RES) { + span->flag_finish = 1; + span_cnt++; + } + + list_for_each_entry(ctx, &(rt_ctx->contexts), list) + if (ctx->smp_opt_dir == SMP_OPT_DIR_RES) { + ctx->flag_finish = 1; + ctx_cnt++; + } + + OTELC_DBG(NOTICE, "marked RESponse channel %d span(s), %d context(s)", span_cnt, ctx_cnt); + } + else { + list_for_each_entry(span, &(rt_ctx->spans), list) + if (FLT_OTEL_CONF_STR_CMP(span->id, id)) { + span->flag_finish = 1; + span_cnt++; + + break; + } + + list_for_each_entry(ctx, &(rt_ctx->contexts), list) + if (FLT_OTEL_CONF_STR_CMP(ctx->id, id)) { + ctx->flag_finish = 1; + ctx_cnt++; + + break; + } + + if (span_cnt > 0) + OTELC_DBG(NOTICE, "marked span '%s'", id); + if (ctx_cnt > 0) + OTELC_DBG(NOTICE, "marked context '%s'", id); + if ((span_cnt + ctx_cnt) == 0) + OTELC_DBG(NOTICE, "cannot find span/context '%s'", id); + } + + retval = span_cnt + ctx_cnt; + + OTELC_RETURN_INT(retval); +} + + +/*** + * NAME + * flt_otel_scope_finish_marked - finish marked spans and contexts + * + * SYNOPSIS + * void flt_otel_scope_finish_marked(const struct flt_otel_runtime_context *rt_ctx, const struct timespec *ts_finish) + * + * ARGUMENTS + * rt_ctx - the runtime context containing spans and contexts + * ts_finish - the monotonic timestamp to use as the span end time + * + * DESCRIPTION + * Ends all spans and destroys all contexts that have been marked for + * finishing by flt_otel_scope_finish_mark(). Each span is ended with the + * timestamp; each context's OTel span context is destroyed. + * The finish flags are cleared after processing. + * + * RETURN VALUE + * This function does not return a value. + */ +void flt_otel_scope_finish_marked(const struct flt_otel_runtime_context *rt_ctx, const struct timespec *ts_finish) +{ + struct flt_otel_scope_span *span; + struct flt_otel_scope_context *ctx; + + OTELC_FUNC("%p, %p", rt_ctx, ts_finish); + + /* End all spans that have been marked for finishing. */ + list_for_each_entry(span, &(rt_ctx->spans), list) + if (span->flag_finish) { + FLT_OTEL_DBG_SCOPE_SPAN("finishing span ", span); + + OTELC_OPSR(span->span, end_with_options, ts_finish, OTELC_SPAN_STATUS_IGNORE, NULL); + + span->flag_finish = 0; + } + + /* Destroy all contexts that have been marked for finishing. */ + list_for_each_entry(ctx, &(rt_ctx->contexts), list) + if (ctx->flag_finish) { + FLT_OTEL_DBG_SCOPE_CONTEXT("finishing context ", ctx); + + if (ctx->context != NULL) + OTELC_OPSR(ctx->context, destroy); + + ctx->flag_finish = 0; + } + + OTELC_RETURN(); +} + + /*** * NAME * flt_otel_scope_free_unused - remove unused spans and contexts diff --git a/addons/otel/src/util.c b/addons/otel/src/util.c index 6c0913f06..20cdbad24 100644 --- a/addons/otel/src/util.c +++ b/addons/otel/src/util.c @@ -519,6 +519,441 @@ int flt_otel_sample_to_str(const struct sample_data *data, char *value, size_t s OTELC_RETURN_INT(retval); } + +/*** + * NAME + * flt_otel_sample_to_value - sample data to OTel value conversion + * + * SYNOPSIS + * int flt_otel_sample_to_value(const char *key, const struct sample_data *data, struct otelc_value *value, char **err) + * + * ARGUMENTS + * key - sample key name (for debug output) + * data - sample data to convert + * value - output OTel value structure + * err - indirect pointer to error message string + * + * DESCRIPTION + * Converts sample data to an otelc_value structure. Boolean samples are + * stored as OTELC_VALUE_BOOL, integer samples as OTELC_VALUE_INT64. All + * other types are converted to a string via flt_otel_sample_to_str() and + * stored as OTELC_VALUE_DATA with heap-allocated storage. + * + * RETURN VALUE + * Returns the size of the converted value, or FLT_OTEL_RET_ERROR on failure. + */ +int flt_otel_sample_to_value(const char *key, const struct sample_data *data, struct otelc_value *value, char **err) +{ + int retval = FLT_OTEL_RET_ERROR; + + OTELC_FUNC("\"%s\", %p, %p, %p:%p", OTELC_STR_ARG(key), data, value, OTELC_DPTR_ARGS(err)); + + if ((data == NULL) || (value == NULL)) + OTELC_RETURN_INT(retval); + + /* Convert the sample value to an otelc_value based on its type. */ + if (data->type == SMP_T_BOOL) { + value->u_type = OTELC_VALUE_BOOL; + value->u.value_bool = data->u.sint ? 1 : 0; + + retval = sizeof(value->u.value_bool); + } + else if (data->type == SMP_T_SINT) { + value->u_type = OTELC_VALUE_INT64; + value->u.value_int64 = data->u.sint; + + retval = sizeof(value->u.value_int64); + } + else { + value->u_type = OTELC_VALUE_DATA; + value->u.value_data = OTELC_MALLOC(global.tune.bufsize); + + if (value->u.value_data == NULL) + FLT_OTEL_ERR("out of memory"); + else + retval = flt_otel_sample_to_str(data, value->u.value_data, global.tune.bufsize, err); + } + + OTELC_RETURN_INT(retval); +} + + +/*** + * NAME + * flt_otel_sample_add_event - span event attribute addition + * + * SYNOPSIS + * static int flt_otel_sample_add_event(struct list *events, struct flt_otel_conf_sample *sample, const struct otelc_value *value) + * + * ARGUMENTS + * events - list of span events (flt_otel_scope_data_event) + * sample - configured sample with event name and key + * value - OTel value to add as an attribute + * + * DESCRIPTION + * Adds a sample value as a span event attribute. Searches the existing + * events list for an event with a matching name; if not found, creates a new + * event entry with an initial attribute array of FLT_OTEL_ATTR_INIT_SIZE + * elements. If the attribute array is full, it is grown by + * FLT_OTEL_ATTR_INC_SIZE elements. The key-value pair is appended to the + * event's attribute array. + * + * RETURN VALUE + * Returns the attribute count for the event, or FLT_OTEL_RET_ERROR on + * failure. + */ +static int flt_otel_sample_add_event(struct list *events, struct flt_otel_conf_sample *sample, const struct otelc_value *value) +{ + struct flt_otel_scope_data_event *ptr, *event = NULL; + struct otelc_kv *attr = NULL; + bool flag_list_insert = 0; + + OTELC_FUNC("%p, %p, %p", events, sample, value); + + if ((events == NULL) || (sample == NULL) || (value == NULL)) + OTELC_RETURN_INT(FLT_OTEL_RET_ERROR); + + /* + * First try to find an event with the same name in the list of events, + * if it succeeds, new data is added to the event found. + */ + if (!LIST_ISEMPTY(events)) + list_for_each_entry(ptr, events, list) + if (strcmp(ptr->name, OTELC_VALUE_STR(&(sample->extra))) == 0) { + event = ptr; + + break; + } + + /* + * If an event with the required name is not found, a new event is added + * to the list. Initially, the number of attributes for the new event + * is set to FLT_OTEL_ATTR_INIT_SIZE. + */ + if (event == NULL) { + event = OTELC_CALLOC(1, sizeof(*event)); + if (event == NULL) + OTELC_RETURN_INT(FLT_OTEL_RET_ERROR); + + event->name = OTELC_STRDUP(OTELC_VALUE_STR(&(sample->extra))); + event->attr = OTELC_CALLOC(FLT_OTEL_ATTR_INIT_SIZE, sizeof(*(event->attr))); + event->cnt = 0; + event->size = FLT_OTEL_ATTR_INIT_SIZE; + if ((event->name == NULL) || (event->attr == NULL)) { + OTELC_SFREE(event->name); + OTELC_SFREE(event->attr); + OTELC_SFREE(event); + + OTELC_RETURN_INT(FLT_OTEL_RET_ERROR); + } + + flag_list_insert = 1; + + OTELC_DBG(DEBUG, "scope event data initialized"); + } + + /* + * In case event attributes are added to an already existing event in + * the list, it is checked whether the number of attributes should be + * increased. If necessary, it will be increased by the amount + * FLT_OTEL_ATTR_INC_SIZE. + */ + if (event->cnt == event->size) { + typeof(event->attr) ptr = OTELC_REALLOC(event->attr, sizeof(*ptr) * (event->size + FLT_OTEL_ATTR_INC_SIZE)); + if (ptr == NULL) + OTELC_RETURN_INT(FLT_OTEL_RET_ERROR); + + event->attr = ptr; + event->size += FLT_OTEL_ATTR_INC_SIZE; + + OTELC_DBG(DEBUG, "scope event data reallocated"); + } + + attr = event->attr + event->cnt++; + attr->key = sample->key; + attr->key_is_dynamic = false; + (void)memcpy(&(attr->value), value, sizeof(attr->value)); + + if (flag_list_insert) { + if (LIST_ISEMPTY(events)) + LIST_INIT(events); + + LIST_INSERT(events, &(event->list)); + } + + OTELC_RETURN_INT(event->cnt); +} + + +/*** + * NAME + * flt_otel_sample_set_status - span status setter + * + * SYNOPSIS + * static int flt_otel_sample_set_status(struct flt_otel_scope_data_status *status, struct flt_otel_conf_sample *sample, const struct otelc_value *value, char **err) + * + * ARGUMENTS + * status - span status structure to populate + * sample - configured sample with status code in extra data + * value - OTel value for the status description + * err - indirect pointer to error message string + * + * DESCRIPTION + * Sets the span status code and description from sample data. The status + * code is taken from the sample's extra field (an int32 value) and the + * description from , which must be a string type. Multiple status + * settings for the same span are rejected with an error. + * + * RETURN VALUE + * Returns 1 on success, or FLT_OTEL_RET_ERROR on failure. + */ +static int flt_otel_sample_set_status(struct flt_otel_scope_data_status *status, struct flt_otel_conf_sample *sample, const struct otelc_value *value, char **err) +{ + OTELC_FUNC("%p, %p, %p, %p:%p", status, sample, value, OTELC_DPTR_ARGS(err)); + + if ((status == NULL) || (sample == NULL) || (value == NULL)) + OTELC_RETURN_INT(FLT_OTEL_RET_ERROR); + + /* + * This scenario should never occur, but the check is still enforced - + * multiple status settings are not allowed within the filter + * configuration for each span event. + */ + if (status->description != NULL) { + FLT_OTEL_ERR("'%s' : span status already set", sample->key); + + OTELC_RETURN_INT(FLT_OTEL_RET_ERROR); + } + else if ((value->u_type != OTELC_VALUE_STRING) && (value->u_type != OTELC_VALUE_DATA)) { + FLT_OTEL_ERR("'%s' : status description must be a string value", sample->key); + + OTELC_RETURN_INT(FLT_OTEL_RET_ERROR); + } + + status->code = sample->extra.u.value_int32; + status->description = OTELC_STRDUP(OTELC_VALUE_STR(value)); + if (status->description == NULL) { + FLT_OTEL_ERR("out of memory"); + + OTELC_RETURN_INT(FLT_OTEL_RET_ERROR); + } + + OTELC_RETURN_INT(1); +} + + +/*** + * NAME + * flt_otel_sample_add_kv - key-value attribute addition + * + * SYNOPSIS + * static int flt_otel_sample_add_kv(struct flt_otel_scope_data_kv *kv, const char *key, const struct otelc_value *value) + * + * ARGUMENTS + * kv - key-value storage (attributes or baggage) + * key - attribute or baggage key name + * value - OTel value to add + * + * DESCRIPTION + * Adds a sample value as a key-value attribute or baggage entry. If the + * key-value array is not yet allocated, it is created with + * FLT_OTEL_ATTR_INIT_SIZE elements via otelc_kv_new(). When the array is + * full, it is grown by FLT_OTEL_ATTR_INC_SIZE elements. The key-value pair + * is appended to the array. + * + * RETURN VALUE + * Returns the current element count, or FLT_OTEL_RET_ERROR on failure. + */ +static int flt_otel_sample_add_kv(struct flt_otel_scope_data_kv *kv, const char *key, const struct otelc_value *value) +{ + struct otelc_kv *attr = NULL; + + OTELC_FUNC("%p, \"%s\", %p", kv, OTELC_STR_ARG(key), value); + + if ((kv == NULL) || (key == NULL) || (value == NULL)) + OTELC_RETURN_INT(FLT_OTEL_RET_ERROR); + + if (kv->attr == NULL) { + kv->attr = otelc_kv_new(FLT_OTEL_ATTR_INIT_SIZE); + if (kv->attr == NULL) + OTELC_RETURN_INT(FLT_OTEL_RET_ERROR); + + kv->cnt = 0; + kv->size = FLT_OTEL_ATTR_INIT_SIZE; + + OTELC_DBG(DEBUG, "scope kv data initialized"); + } + + if (kv->cnt == kv->size) { + typeof(kv->attr) ptr = OTELC_REALLOC(kv->attr, sizeof(*ptr) * (kv->size + FLT_OTEL_ATTR_INC_SIZE)); + if (ptr == NULL) + OTELC_RETURN_INT(FLT_OTEL_RET_ERROR); + + kv->attr = ptr; + kv->size += FLT_OTEL_ATTR_INC_SIZE; + + OTELC_DBG(DEBUG, "scope kv data reallocated"); + } + + attr = kv->attr + kv->cnt++; + attr->key = (typeof(attr->key))key; + attr->key_is_dynamic = false; + (void)memcpy(&(attr->value), value, sizeof(attr->value)); + + OTELC_RETURN_INT(kv->cnt); +} + + +/*** + * NAME + * flt_otel_sample_add - top-level sample evaluator + * + * SYNOPSIS + * int flt_otel_sample_add(struct stream *s, uint dir, struct flt_otel_conf_sample *sample, struct flt_otel_scope_data *data, int type, char **err) + * + * ARGUMENTS + * s - current stream + * dir - the sample fetch direction (SMP_OPT_DIR_REQ/RES) + * sample - configured sample definition + * data - scope data to populate + * type - sample type (FLT_OTEL_EVENT_SAMPLE_*) + * err - indirect pointer to error message string + * + * DESCRIPTION + * Processes all sample expressions for a configured sample definition, + * converts the results, and dispatches to the appropriate handler. For + * single-expression attributes and events, native type preservation is + * attempted via flt_otel_sample_to_value(). For multi-expression samples, + * all results are concatenated into a string buffer. The final value is + * dispatched to flt_otel_sample_add_kv() for attributes and baggage, + * flt_otel_sample_add_event() for events, or flt_otel_sample_set_status() + * for status. + * + * RETURN VALUE + * Returns a negative value if an error occurs, 0 if it needs to wait, + * any other value otherwise. + */ +int flt_otel_sample_add(struct stream *s, uint dir, struct flt_otel_conf_sample *sample, struct flt_otel_scope_data *data, int type, char **err) +{ + const struct flt_otel_conf_sample_expr *expr; + struct sample smp; + struct otelc_value value; + struct buffer buffer; + int idx = 0, rc, retval = FLT_OTEL_RET_OK; + + OTELC_FUNC("%p, %u, %p, %p, %d, %p:%p", s, dir, sample, data, type, OTELC_DPTR_ARGS(err)); + + FLT_OTEL_DBG_CONF_SAMPLE("sample ", sample); + + (void)memset(&value, 0, sizeof(value)); + (void)memset(&buffer, 0, sizeof(buffer)); + + list_for_each_entry(expr, &(sample->exprs), list) { + FLT_OTEL_DBG_CONF_SAMPLE_EXPR("sample expression ", expr); + + (void)memset(&smp, 0, sizeof(smp)); + + if (sample_process(s->be, s->sess, s, dir | SMP_OPT_FINAL, expr->expr, &smp) != NULL) { + OTELC_DBG(DEBUG, "data type %d: '%s'", smp.data.type, expr->fmt_expr); + } else { + OTELC_DBG(NOTICE, "WARNING: failed to fetch '%s' value", expr->fmt_expr); + + /* + * In case the fetch failed, we will set the result + * (sample) to an empty static string. + */ + (void)memset(&(smp.data), 0, sizeof(smp.data)); + smp.data.type = SMP_T_STR; + smp.data.u.str.area = ""; + } + + /* + * If we have only one expression to process, then the data + * type that is the result of the expression is converted to + * an equivalent data type (if possible) that is written to + * the tracer. + * + * If conversion is not possible, or if we have multiple + * expressions to process, then the result is converted to + * a string and as such sent to the tracer. + */ + if ((sample->num_exprs == 1) && ((type == FLT_OTEL_EVENT_SAMPLE_ATTRIBUTE) || (type == FLT_OTEL_EVENT_SAMPLE_EVENT))) { + if (flt_otel_sample_to_value(sample->key, &(smp.data), &value, err) == FLT_OTEL_RET_ERROR) + retval = FLT_OTEL_RET_ERROR; + } else { + if (buffer.area == NULL) { + chunk_init(&buffer, OTELC_CALLOC(1, global.tune.bufsize), global.tune.bufsize); + if (buffer.area == NULL) { + FLT_OTEL_ERR("out of memory"); + + retval = FLT_OTEL_RET_ERROR; + + break; + } + } + + rc = flt_otel_sample_to_str(&(smp.data), buffer.area + buffer.data, buffer.size - buffer.data, err); + if (rc == FLT_OTEL_RET_ERROR) { + retval = FLT_OTEL_RET_ERROR; + } else { + buffer.data += rc; + + if (sample->num_exprs == ++idx) { + value.u_type = OTELC_VALUE_DATA; + value.u.value_data = buffer.area; + } + } + } + } + + /* Dispatch the evaluated value to the appropriate collection. */ + if (retval == FLT_OTEL_RET_ERROR) { + /* Do nothing. */ + } + else if (type == FLT_OTEL_EVENT_SAMPLE_ATTRIBUTE) { + retval = flt_otel_sample_add_kv(&(data->attributes), sample->key, &value); + if (retval == FLT_OTEL_RET_ERROR) + FLT_OTEL_ERR("out of memory"); + } + else if (type == FLT_OTEL_EVENT_SAMPLE_EVENT) { + retval = flt_otel_sample_add_event(&(data->events), sample, &value); + if (retval == FLT_OTEL_RET_ERROR) + FLT_OTEL_ERR("out of memory"); + } + else if (type == FLT_OTEL_EVENT_SAMPLE_BAGGAGE) { + retval = flt_otel_sample_add_kv(&(data->baggage), sample->key, &value); + if (retval == FLT_OTEL_RET_ERROR) + FLT_OTEL_ERR("out of memory"); + } + else if (type == FLT_OTEL_EVENT_SAMPLE_STATUS) { + retval = flt_otel_sample_set_status(&(data->status), sample, &value, err); + } + else { + FLT_OTEL_ERR("invalid event sample type: %d", type); + + retval = FLT_OTEL_RET_ERROR; + } + + /* + * Free dynamically allocated value data that was not transferred to + * a key-value array. For ATTRIBUTE, EVENT, and BAGGAGE, the value + * pointer is shallow-copied into the kv array on success and will be + * freed by otelc_kv_destroy(). For STATUS, the handler creates its + * own copy, so the original must be freed. On any error, no handler + * consumed the value. + */ + if ((retval != FLT_OTEL_RET_ERROR) && (type != FLT_OTEL_EVENT_SAMPLE_STATUS)) + /* Do nothing. */; + else if (buffer.area != NULL) + OTELC_SFREE(buffer.area); + else if (value.u_type == OTELC_VALUE_DATA) + OTELC_SFREE(value.u.value_data); + + flt_otel_scope_data_dump(data); + + OTELC_RETURN_INT(retval); +} + /* * Local variables: * c-indent-level: 8