mirror of
https://github.com/haproxy/haproxy.git
synced 2026-04-13 04:46:15 -04:00
MEDIUM: otel: implemented scope execution and span management
Implemented the scope execution engine that creates OTel spans, evaluates
sample expressions to collect telemetry data, and manages span lifecycle
during request and response processing.
The scope runner flt_otel_scope_run() was expanded from a stub into a
complete implementation that evaluates ACL conditions on the scope,
extracts span contexts from HTTP headers when configured, iterates over
the scope's span definitions calling flt_otel_scope_run_span() for each,
marks and finishes completed spans, and cleans up unused runtime
resources.
The span runner flt_otel_scope_run_span() creates OTel spans via the
tracer with optional parent references (from other spans or extracted
contexts), collects telemetry by calling flt_otel_sample_add() for each
configured attribute, event, baggage and status entry, then applies the
collected data to the span (attributes, events with their own key-value
arrays, baggage items, and status code with description) and injects the
span context into HTTP headers when configured.
The sample evaluation layer converts HAProxy sample expressions into OTel
telemetry data. flt_otel_sample_add() evaluates each sample expression
against the stream, converts the result via flt_otel_sample_to_value()
which preserves native types (booleans as OTELC_VALUE_BOOL, integers as
OTELC_VALUE_INT64, all others as strings), and routes the key-value pair
to the appropriate collector based on the sample type (attribute, event,
baggage, or status). The key-value arrays grow dynamically using the
FLT_OTEL_ATTR_INIT_SIZE and FLT_OTEL_ATTR_INC_SIZE constants.
Span finishing is handled in two phases: flt_otel_scope_finish_mark()
marks spans and contexts for completion using exact name matching or
wildcards ("*" for all, "*req*" for request-direction, "*res*" for
response-direction), and flt_otel_scope_finish_marked() ends all marked
spans with a common monotonic timestamp and destroys their contexts.
This commit is contained in:
parent
3184470339
commit
bab0ea7b77
8 changed files with 818 additions and 2 deletions
|
|
@ -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_ */
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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_ */
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -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 <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
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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 <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
|
||||
|
|
|
|||
|
|
@ -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 <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
|
||||
|
|
|
|||
Loading…
Reference in a new issue