#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_ */
/*
/* 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)
#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. */
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);
/* 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);
/* 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_ */
/*
#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 <data>, then
+ * injects the span context into HTTP headers if configured in <conf_span>.
+ *
+ * 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
* 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);
}
}
+/***
+ * 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 <id> string
+ *
+ * DESCRIPTION
+ * Marks spans and contexts for finishing. The <id> 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
+ * <ts_finish> 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
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 <value>, 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