diff --git a/addons/otel/include/conf.h b/addons/otel/include/conf.h index 99c37cad4..08e82b845 100644 --- a/addons/otel/include/conf.h +++ b/addons/otel/include/conf.h @@ -47,12 +47,13 @@ #define FLT_OTEL_DBG_CONF_CONTEXT(h,p) \ OTELC_DBG_STRUCT(DEBUG, h, h FLT_OTEL_CONF_HDR_FMT "0x%02hhx }", (p), FLT_OTEL_CONF_HDR_ARGS(p, id), (p)->flags) -#define FLT_OTEL_DBG_CONF_SPAN(h,p) \ - OTELC_DBG_STRUCT(DEBUG, h, h FLT_OTEL_CONF_HDR_FMT "'%s' %zu %s' %zu %hhu 0x%02hhx %s %s %s %s }", \ - (p), FLT_OTEL_CONF_HDR_ARGS(p, id), FLT_OTEL_STR_HDR_ARGS(p, ref_id), \ - FLT_OTEL_STR_HDR_ARGS(p, ctx_id), (p)->flag_root, (p)->ctx_flags, \ - flt_otel_list_dump(&((p)->attributes)), flt_otel_list_dump(&((p)->events)), \ - flt_otel_list_dump(&((p)->baggages)), flt_otel_list_dump(&((p)->statuses))) +#define FLT_OTEL_DBG_CONF_SPAN(h,p) \ + OTELC_DBG_STRUCT(DEBUG, h, h FLT_OTEL_CONF_HDR_FMT "'%s' %zu %s' %zu %hhu 0x%02hhx %s %s %s %s %s }", \ + (p), FLT_OTEL_CONF_HDR_ARGS(p, id), FLT_OTEL_STR_HDR_ARGS(p, ref_id), \ + FLT_OTEL_STR_HDR_ARGS(p, ctx_id), (p)->flag_root, (p)->ctx_flags, \ + flt_otel_list_dump(&((p)->links)), flt_otel_list_dump(&((p)->attributes)), \ + flt_otel_list_dump(&((p)->events)), flt_otel_list_dump(&((p)->baggages)), \ + flt_otel_list_dump(&((p)->statuses))) #define FLT_OTEL_DBG_CONF_SCOPE(h,p) \ OTELC_DBG_STRUCT(DEBUG, h, h FLT_OTEL_CONF_HDR_FMT "%hhu %d %u %s %p %s %s %s }", (p), \ @@ -138,6 +139,11 @@ struct flt_otel_conf_context { uint8_t flags; /* The type of storage from which the span context is extracted. */ }; +/* flt_otel_conf_span->links */ +struct flt_otel_conf_link { + FLT_OTEL_CONF_HDR(span); /* The list containing link names. */ +}; + /* * Span configuration within a scope. * flt_otel_conf_scope->spans @@ -148,6 +154,7 @@ struct flt_otel_conf_span { FLT_OTEL_CONF_STR(ctx_id); /* The span context name, if used. */ uint8_t ctx_flags; /* The type of storage used for the span context. */ bool flag_root; /* Whether this is a root span. */ + struct list links; /* The set of linked span names. */ struct list attributes; /* The set of key:value attributes. */ struct list events; /* The set of events with key-value attributes. */ struct list baggages; /* The set of key:value baggage items. */ diff --git a/addons/otel/include/conf_funcs.h b/addons/otel/include/conf_funcs.h index af19aa9e1..4ca6da943 100644 --- a/addons/otel/include/conf_funcs.h +++ b/addons/otel/include/conf_funcs.h @@ -101,6 +101,7 @@ FLT_OTEL_CONF_FUNC_DECL(str) FLT_OTEL_CONF_FUNC_DECL(ph) FLT_OTEL_CONF_FUNC_DECL(sample_expr) FLT_OTEL_CONF_FUNC_DECL(sample) +FLT_OTEL_CONF_FUNC_DECL(link) FLT_OTEL_CONF_FUNC_DECL(context) FLT_OTEL_CONF_FUNC_DECL(span) FLT_OTEL_CONF_FUNC_DECL(scope) diff --git a/addons/otel/include/parser.h b/addons/otel/include/parser.h index 9eb45e612..282ffd8f6 100644 --- a/addons/otel/include/parser.h +++ b/addons/otel/include/parser.h @@ -19,6 +19,7 @@ #define FLT_OTEL_PARSE_SPAN_ROOT "root" #define FLT_OTEL_PARSE_SPAN_PARENT "parent" +#define FLT_OTEL_PARSE_SPAN_LINK "link" #define FLT_OTEL_PARSE_CTX_AUTONAME "-" #define FLT_OTEL_PARSE_CTX_IGNORE_NAME '-' #define FLT_OTEL_PARSE_CTX_USE_HEADERS "use-headers" @@ -73,7 +74,8 @@ */ #define FLT_OTEL_PARSE_SCOPE_DEFINES \ FLT_OTEL_PARSE_SCOPE_DEF( ID, 0, CHAR, 2, 2, "otel-scope", " ") \ - FLT_OTEL_PARSE_SCOPE_DEF( SPAN, 0, NONE, 2, 7, "span", " [] [root]") \ + FLT_OTEL_PARSE_SCOPE_DEF( SPAN, 0, NONE, 2, 7, "span", " [] [] [root]") \ + FLT_OTEL_PARSE_SCOPE_DEF( LINK, 1, NONE, 2, 0, "link", " ...") \ FLT_OTEL_PARSE_SCOPE_DEF( ATTRIBUTE, 1, NONE, 3, 0, "attribute", " ...") \ FLT_OTEL_PARSE_SCOPE_DEF( EVENT, 1, NONE, 4, 0, "event", " ...") \ FLT_OTEL_PARSE_SCOPE_DEF( BAGGAGE, 1, VAR, 3, 0, "baggage", " ...") \ diff --git a/addons/otel/include/scope.h b/addons/otel/include/scope.h index 7570e6222..669a8ad67 100644 --- a/addons/otel/include/scope.h +++ b/addons/otel/include/scope.h @@ -29,10 +29,10 @@ #define FLT_OTEL_DBG_SCOPE_DATA_KV_FMT "%p:{ %p %zu %zu }" #define FLT_OTEL_DBG_SCOPE_DATA_KV_ARGS(p) &(p), (p).attr, (p).cnt, (p).size -#define FLT_OTEL_DBG_SCOPE_DATA(h,p) \ - OTELC_DBG(DEBUG, h "%p:{ " FLT_OTEL_DBG_SCOPE_DATA_KV_FMT " " FLT_OTEL_DBG_SCOPE_DATA_KV_FMT " %s }", (p), \ - FLT_OTEL_DBG_SCOPE_DATA_KV_ARGS((p)->baggage), FLT_OTEL_DBG_SCOPE_DATA_KV_ARGS((p)->attributes), \ - flt_otel_list_dump(&((p)->events))) +#define FLT_OTEL_DBG_SCOPE_DATA(h,p) \ + OTELC_DBG(DEBUG, h "%p:{ " FLT_OTEL_DBG_SCOPE_DATA_KV_FMT " " FLT_OTEL_DBG_SCOPE_DATA_KV_FMT " %s %s }", (p), \ + FLT_OTEL_DBG_SCOPE_DATA_KV_ARGS((p)->baggage), FLT_OTEL_DBG_SCOPE_DATA_KV_ARGS((p)->attributes), \ + flt_otel_list_dump(&((p)->events)), flt_otel_list_dump(&((p)->links))) #define FLT_OTEL_DBG_RUNTIME_CONTEXT(h,p) \ OTELC_DBG(DEBUG, h "%p:{ %p %p '%s' %hhu %hhu 0x%02hhx 0x%08x %u %d %s %s }", (p), \ @@ -64,15 +64,25 @@ struct flt_otel_scope_data_event { struct list list; /* Used to chain this structure. */ }; +/* Span link referencing another span or span context. */ +struct flt_otel_scope_data_link { + struct otelc_span *span; /* Linked span, or NULL. */ + struct otelc_span_context *context; /* Linked span context, or NULL. */ + struct list list; /* Used to chain this structure. */ +}; + +/* Span status code and description. */ struct flt_otel_scope_data_status { int code; /* OTELC_SPAN_STATUS_* value. */ char *description; /* Span status description string. */ }; +/* Aggregated runtime data collected during scope execution. */ struct flt_otel_scope_data { struct flt_otel_scope_data_kv baggage; /* Defined scope baggage. */ struct flt_otel_scope_data_kv attributes; /* Defined scope attributes. */ struct list events; /* Defined scope events. */ + struct list links; /* Defined scope links. */ struct flt_otel_scope_data_status status; /* Defined scope status. */ }; diff --git a/addons/otel/src/conf.c b/addons/otel/src/conf.c index f2f46b1d3..0914a962d 100644 --- a/addons/otel/src/conf.c +++ b/addons/otel/src/conf.c @@ -95,6 +95,53 @@ FLT_OTEL_CONF_FUNC_FREE(str, str, ) +/*** + * NAME + * flt_otel_conf_link_init - conf_link structure allocation + * + * SYNOPSIS + * struct flt_otel_conf_link *flt_otel_conf_link_init(const char *id, int line, struct list *head, char **err) + * + * ARGUMENTS + * id - identifier string to duplicate + * line - configuration file line number + * head - list to append to (or NULL) + * err - indirect pointer to error message string + * + * DESCRIPTION + * Allocates and initializes a conf_link structure for a span link + * reference. The string is duplicated and stored as the linked + * span name. If is non-NULL, the structure is appended to + * the list. + * + * RETURN VALUE + * Returns a pointer to the initialized structure, or NULL on failure. + */ +FLT_OTEL_CONF_FUNC_INIT(link, span, ) + + +/*** + * NAME + * flt_otel_conf_link_free - conf_link structure deallocation + * + * SYNOPSIS + * void flt_otel_conf_link_free(struct flt_otel_conf_link **ptr) + * + * ARGUMENTS + * ptr - a pointer to the address of a structure + * + * DESCRIPTION + * Deallocates memory used by the flt_otel_conf_link structure and its + * contents, then removes it from the list of structures of that type. + * + * RETURN VALUE + * This function does not return a value. + */ +FLT_OTEL_CONF_FUNC_FREE(link, span, + FLT_OTEL_DBG_CONF_HDR("- conf_link free ", *ptr, span); +) + + /*** * NAME * flt_otel_conf_ph_init - conf_ph placeholder structure allocation @@ -401,6 +448,7 @@ FLT_OTEL_CONF_FUNC_FREE(context, id, * Returns a pointer to the initialized structure, or NULL on failure. */ FLT_OTEL_CONF_FUNC_INIT(span, id, + LIST_INIT(&(retptr->links)); LIST_INIT(&(retptr->attributes)); LIST_INIT(&(retptr->events)); LIST_INIT(&(retptr->baggages)); @@ -430,6 +478,7 @@ FLT_OTEL_CONF_FUNC_FREE(span, id, OTELC_SFREE((*ptr)->ref_id); OTELC_SFREE((*ptr)->ctx_id); + FLT_OTEL_LIST_DESTROY(link, &((*ptr)->links)); FLT_OTEL_LIST_DESTROY(sample, &((*ptr)->attributes)); FLT_OTEL_LIST_DESTROY(sample, &((*ptr)->events)); FLT_OTEL_LIST_DESTROY(sample, &((*ptr)->baggages)); diff --git a/addons/otel/src/event.c b/addons/otel/src/event.c index 38a064458..e2c6ddffa 100644 --- a/addons/otel/src/event.c +++ b/addons/otel/src/event.c @@ -54,6 +54,18 @@ static int flt_otel_scope_run_span(struct stream *s, struct filter *f, struct ch OTELC_RETURN_INT(FLT_OTEL_RET_ERROR); } + /* Add all resolved span links to the current span. */ + if (!LIST_ISEMPTY(&(data->links))) { + struct flt_otel_scope_data_link *link; + + list_for_each_entry(link, &(data->links), list) { + OTELC_DBG(DEBUG, "adding link %p %p", link->span, link->context); + + if (OTELC_OPS(span->span, add_link, link->span, link->context, NULL, 0) == -1) + retval = 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) @@ -245,6 +257,61 @@ int flt_otel_scope_run(struct stream *s, struct filter *f, struct channel *chn, if (span == NULL) retval = FLT_OTEL_RET_ERROR; + /* + * Resolve configured span links against the runtime context. + * Each link name is looked up first in the active spans, then + * in the extracted contexts. + */ + if (!LIST_ISEMPTY(&(conf_span->links))) { + struct flt_otel_runtime_context *rt_ctx = FLT_OTEL_RT_CTX(f->ctx); + struct flt_otel_conf_link *conf_link; + + list_for_each_entry(conf_link, &(conf_span->links), list) { + struct flt_otel_scope_data_link *data_link; + struct otelc_span *link_span = NULL; + struct otelc_span_context *link_ctx = NULL; + struct flt_otel_scope_span *sc_span; + struct flt_otel_scope_context *sc_ctx; + + /* Try to find a matching span first. */ + list_for_each_entry(sc_span, &(rt_ctx->spans), list) + if (FLT_OTEL_CONF_STR_CMP(sc_span->id, conf_link->span)) { + link_span = sc_span->span; + + break; + } + + /* If no span found, try to find a matching context. */ + if (link_span == NULL) { + list_for_each_entry(sc_ctx, &(rt_ctx->contexts), list) + if (FLT_OTEL_CONF_STR_CMP(sc_ctx->id, conf_link->span)) { + link_ctx = sc_ctx->context; + + break; + } + } + + if ((link_span == NULL) && (link_ctx == NULL)) { + OTELC_DBG(NOTICE, "WARNING: cannot find linked span/context '%s'", conf_link->span); + + continue; + } + + data_link = OTELC_CALLOC(1, sizeof(*data_link)); + if (data_link == NULL) { + retval = FLT_OTEL_RET_ERROR; + + break; + } + + data_link->span = link_span; + data_link->context = link_ctx; + LIST_APPEND(&(data.links), &(data_link->list)); + + OTELC_DBG(DEBUG, "resolved link '%s' -> %p %p", conf_link->span, link_span, link_ctx); + } + } + list_for_each_entry(sample, &(conf_span->attributes), list) { OTELC_DBG(DEBUG, "adding attribute '%s' -> '%s'", sample->key, sample->fmt_string); diff --git a/addons/otel/src/parser.c b/addons/otel/src/parser.c index 18627b62b..bcb3b71eb 100644 --- a/addons/otel/src/parser.c +++ b/addons/otel/src/parser.c @@ -955,6 +955,14 @@ static int flt_otel_parse_cfg_scope(const char *file, int line, char **args, int else FLT_OTEL_PARSE_ERR(&err, "'%s' : too few arguments (use '%s%s')", args[i], pdata->name, pdata->usage); } + else if (FLT_OTEL_PARSE_KEYWORD(i, FLT_OTEL_PARSE_SPAN_LINK)) { + if (FLT_OTEL_ARG_ISVALID(i + 1)) { + if (flt_otel_conf_link_init(args[++i], line, &(flt_otel_current_span->links), &err) == NULL) + retval |= ERR_ABORT | ERR_ALERT; + } else { + FLT_OTEL_PARSE_ERR(&err, "'%s' : too few arguments (use '%s%s')", args[i], pdata->name, pdata->usage); + } + } else { FLT_OTEL_PARSE_ERR(&err, "'%s' : invalid argument (use '%s%s')", args[i], pdata->name, pdata->usage); } @@ -968,6 +976,11 @@ static int flt_otel_parse_cfg_scope(const char *file, int line, char **args, int OTELC_DBG(DEBUG, "new span '%s' without reference", flt_otel_current_span->id); } } + else if (pdata->keyword == FLT_OTEL_PARSE_SCOPE_LINK) { + for (i = 1; !(retval & ERR_CODE) && FLT_OTEL_ARG_ISVALID(i); i++) + if (flt_otel_conf_link_init(args[i], line, &(flt_otel_current_span->links), &err) == NULL) + retval |= ERR_ABORT | ERR_ALERT; + } else if (pdata->keyword == FLT_OTEL_PARSE_SCOPE_ATTRIBUTE) { retval = flt_otel_parse_cfg_sample(file, line, args, 2, 0, NULL, &(flt_otel_current_span->attributes), &err); } diff --git a/addons/otel/src/scope.c b/addons/otel/src/scope.c index cdef4c19f..eca2741ad 100644 --- a/addons/otel/src/scope.c +++ b/addons/otel/src/scope.c @@ -418,6 +418,17 @@ void flt_otel_scope_data_dump(const struct flt_otel_scope_data *data) OTELC_DBG(WORKER, "}"); } + if (LIST_ISEMPTY(&(data->links))) { + OTELC_DBG(WORKER, "links %p:{ }", &(data->links)); + } else { + struct flt_otel_scope_data_link *link; + + OTELC_DBG(WORKER, "links %p:{", &(data->links)); + list_for_each_entry(link, &(data->links), list) + OTELC_DBG(WORKER, " %p %p", link->span, link->context); + OTELC_DBG(WORKER, "}"); + } + if ((data->status.code == 0) && (data->status.description == NULL)) OTELC_DBG(WORKER, "status %p:{ }", &(data->status)); else @@ -453,6 +464,7 @@ void flt_otel_scope_data_init(struct flt_otel_scope_data *ptr) (void)memset(ptr, 0, sizeof(*ptr)); LIST_INIT(&(ptr->events)); + LIST_INIT(&(ptr->links)); OTELC_RETURN(); } @@ -479,6 +491,7 @@ void flt_otel_scope_data_init(struct flt_otel_scope_data *ptr) void flt_otel_scope_data_free(struct flt_otel_scope_data *ptr) { struct flt_otel_scope_data_event *event, *event_back; + struct flt_otel_scope_data_link *link, *link_back; OTELC_FUNC("%p", ptr); @@ -495,6 +508,9 @@ void flt_otel_scope_data_free(struct flt_otel_scope_data *ptr) OTELC_SFREE(event->name); OTELC_SFREE(event); } + /* Free all resolved link entries. */ + list_for_each_entry_safe(link, link_back, &(ptr->links), list) + OTELC_SFREE(link); OTELC_SFREE(ptr->status.description); (void)memset(ptr, 0, sizeof(*ptr)); diff --git a/addons/otel/test/sa/otel.cfg b/addons/otel/test/sa/otel.cfg index 147222b5f..69f2f32ef 100644 --- a/addons/otel/test/sa/otel.cfg +++ b/addons/otel/test/sa/otel.cfg @@ -95,7 +95,7 @@ otel-event on-http-body-request otel-scope frontend_http_request - span "Frontend HTTP request" parent "HTTP body request" + span "Frontend HTTP request" parent "HTTP body request" link "HAProxy session" attribute "http.method" method attribute "http.url" url attribute "http.version" str("HTTP/") req.ver @@ -147,6 +147,7 @@ otel-scope server_session_start span "Server session" parent "HAProxy session" + link "HAProxy session" "Client session" finish "Process sticking rules request" otel-event on-server-session-start