diff --git a/addons/otel/include/conf.h b/addons/otel/include/conf.h index 6986e0da3..a9ba38075 100644 --- a/addons/otel/include/conf.h +++ b/addons/otel/include/conf.h @@ -205,6 +205,11 @@ struct flt_otel_counters { uint64_t htx[2]; /* htx_is_empty() function result counter. */ } event[FLT_OTEL_EVENT_MAX]; #endif + +#ifdef FLT_OTEL_USE_COUNTERS + uint64_t attached[4]; /* [run rate-limit disabled error] */ + uint64_t disabled[2]; /* How many times stream processing is disabled. */ +#endif }; /* The OpenTelemetry filter configuration. */ diff --git a/addons/otel/include/config.h b/addons/otel/include/config.h index 5532124f1..cc9340faa 100644 --- a/addons/otel/include/config.h +++ b/addons/otel/include/config.h @@ -11,6 +11,11 @@ #define USE_POOL_OTEL_RUNTIME_CONTEXT #define USE_TRASH_CHUNK +/* Enable per-event and per-stream diagnostic counters in debug builds. */ +#if defined(DEBUG_OTEL) && !defined(FLT_OTEL_USE_COUNTERS) +# define FLT_OTEL_USE_COUNTERS +#endif + #define FLT_OTEL_ID_MAXLEN 64 /* Maximum identifier length. */ #define FLT_OTEL_DEBUG_LEVEL 0b11101111111 /* Default debug bitmask. */ diff --git a/addons/otel/include/debug.h b/addons/otel/include/debug.h new file mode 100644 index 000000000..5a8b6cbc0 --- /dev/null +++ b/addons/otel/include/debug.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#ifndef _OTEL_DEBUG_H_ +#define _OTEL_DEBUG_H_ + +#ifdef DEBUG_FULL +# define DEBUG_OTEL +#endif + +/* + * FLT_OTEL_DBG_ARGS - include extra debug-only function parameters. + * FLT_OTEL_DBG_BUF - dump a buffer structure for debugging. + * + * When DEBUG_OTEL is not defined, these expand to nothing. + */ +#ifdef DEBUG_OTEL +# define FLT_OTEL_DBG_ARGS(a, ...) a, ##__VA_ARGS__ +# define FLT_OTEL_DBG_BUF(l,a) OTELC_DBG(l, "%p:{ %zu %p %zu %zu }", (a), (a)->size, (a)->area, (a)->data, (a)->head) +#else +# define FLT_OTEL_DBG_ARGS(...) +# define FLT_OTEL_DBG_BUF(...) while (0) +#endif /* DEBUG_OTEL */ + +/* + * ON | NOLOGNORM | + * -----+-----------+------------- + * 0 | 0 | no log + * 0 | 1 | no log + * 1 | 0 | log all + * 1 | 1 | log errors + * -----+-----------+------------- + */ +#define FLT_OTEL_LOG(l,f, ...) \ + do { \ + if (!(conf->instr->logging & FLT_OTEL_LOGGING_ON)) \ + OTELC_DBG(DEBUG, "NOLOG[%d]: [" FLT_OTEL_SCOPE "]: [%s] " f, (l), conf->id, ##__VA_ARGS__); \ + else if ((conf->instr->logging & FLT_OTEL_LOGGING_NOLOGNORM) && ((l) > LOG_ERR)) \ + OTELC_DBG(NOTICE, "NOLOG[%d]: [" FLT_OTEL_SCOPE "]: [%s] " f, (l), conf->id, ##__VA_ARGS__); \ + else { \ + send_log(&(conf->instr->proxy_log), (l), "[" FLT_OTEL_SCOPE "]: [%s] " f "\n", conf->id, ##__VA_ARGS__); \ + \ + OTELC_DBG(INFO, "LOG[%d]: %s", (l), logline); \ + } \ + } while (0) + +#endif /* _OTEL_DEBUG_H_ */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + * + * vi: noexpandtab shiftwidth=8 tabstop=8 + */ diff --git a/addons/otel/include/event.h b/addons/otel/include/event.h index 8553a4ec4..3ad528f7a 100644 --- a/addons/otel/include/event.h +++ b/addons/otel/include/event.h @@ -123,6 +123,10 @@ struct flt_otel_event_data { /* 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]; + +/* 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); + #endif /* _OTEL_EVENT_H_ */ /* diff --git a/addons/otel/include/filter.h b/addons/otel/include/filter.h index cb495f90d..86f2b835d 100644 --- a/addons/otel/include/filter.h +++ b/addons/otel/include/filter.h @@ -37,6 +37,10 @@ enum FLT_OTEL_RET_enum { extern const char *otel_flt_id; extern struct flt_ops flt_otel_ops; + +/* Check whether the OTel filter is disabled for a stream. */ +bool flt_otel_is_disabled(const struct filter *f FLT_OTEL_DBG_ARGS(, int event)); + #endif /* _OTEL_FILTER_H_ */ /* diff --git a/addons/otel/include/include.h b/addons/otel/include/include.h index e154cfeb1..103166b4d 100644 --- a/addons/otel/include/include.h +++ b/addons/otel/include/include.h @@ -26,6 +26,7 @@ #include #include "config.h" +#include "debug.h" #include "define.h" #include "event.h" #include "conf.h" diff --git a/addons/otel/include/util.h b/addons/otel/include/util.h index 9833d8b55..5db8cb874 100644 --- a/addons/otel/include/util.h +++ b/addons/otel/include/util.h @@ -24,6 +24,21 @@ /* Dump configuration arguments for debugging. */ void flt_otel_args_dump(const char **args); +/* Return a label string identifying a channel direction. */ +const char *flt_otel_chn_label(const struct channel *chn); + +/* Return the proxy mode string for a stream. */ +const char *flt_otel_pr_mode(const struct stream *s); + +/* Return the stream processing position as a string. */ +const char *flt_otel_stream_pos(const struct stream *s); + +/* Return the filter type string for a filter instance. */ +const char *flt_otel_type(const struct filter *f); + +/* Return the analyzer name string for an analyzer bit. */ +const char *flt_otel_analyzer(uint an_bit); + /* Dump a linked list of configuration items as a string. */ const char *flt_otel_list_dump(const struct list *head); #endif diff --git a/addons/otel/src/event.c b/addons/otel/src/event.c index 00a838bc1..503a94702 100644 --- a/addons/otel/src/event.c +++ b/addons/otel/src/event.c @@ -9,6 +9,109 @@ 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 - scope execution engine + * + * SYNOPSIS + * 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) + * + * ARGUMENTS + * s - the stream being processed + * f - the filter instance + * chn - the channel for context extraction and injection + * conf_scope - the scope configuration to execute + * ts_steady - the monotonic timestamp, or NULL to use current time + * ts_system - the wall-clock timestamp, or NULL to use current time + * dir - the sample fetch direction (SMP_OPT_DIR_REQ/RES) + * err - indirect pointer to error message string + * + * DESCRIPTION + * Executes a complete scope: evaluates ACL conditions, extracts contexts + * from HTTP headers, iterates over configured spans (resolving links, + * evaluating sample expressions for attributes, events, baggage and status), + * calls flt_otel_scope_run_span() for each, processes metric instruments, + * then marks and finishes completed spans. + * + * 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) +{ + 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); +} + + +/*** + * NAME + * flt_otel_event_run - top-level event dispatcher + * + * SYNOPSIS + * int flt_otel_event_run(struct stream *s, struct filter *f, struct channel *chn, int event, char **err) + * + * ARGUMENTS + * s - the stream being processed + * f - the filter instance + * chn - the channel being analyzed + * event - the event index (FLT_OTEL_EVENT_*) + * err - indirect pointer to error message string + * + * DESCRIPTION + * Top-level event dispatcher called from filter callbacks. It iterates over + * all scopes matching the index and calls flt_otel_scope_run() for + * each. All spans within a single event share the same monotonic and + * wall-clock timestamps. + * + * RETURN VALUE + * Returns FLT_OTEL_RET_OK on success, FLT_OTEL_RET_ERROR on failure. + */ +int flt_otel_event_run(struct stream *s, struct filter *f, struct channel *chn, int event, char **err) +{ + struct flt_otel_conf *conf = FLT_OTEL_CONF(f); + struct flt_otel_conf_scope *conf_scope; + struct timespec ts_steady, ts_system; + int retval = FLT_OTEL_RET_OK; + + OTELC_FUNC("%p, %p, %p, %d, %p:%p", s, f, chn, event, 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 event '%s' %d %s", flt_otel_event_data[event].name, event, flt_otel_event_data[event].an_name); + +#ifdef DEBUG_OTEL + _HA_ATOMIC_ADD(conf->cnt.event[event].htx + ((chn == NULL) ? 1 : (htx_is_empty(htxbuf(&(chn->buf))) ? 1 : 0)), 1); +#endif + + FLT_OTEL_RT_CTX(f->ctx)->analyzers |= flt_otel_event_data[event].an_bit; + + /* All spans should be created/completed at the same time. */ + (void)clock_gettime(CLOCK_MONOTONIC, &ts_steady); + (void)clock_gettime(CLOCK_REALTIME, &ts_system); + + /* + * It is possible that there are defined multiple scopes that use the + * same event. Therefore, there must not be a 'break' here, ie an exit + * from the 'for' loop. + */ + list_for_each_entry(conf_scope, &(conf->scopes), list) { + if (conf_scope->event != event) + /* Do nothing. */; + else if (!conf_scope->flag_used) + OTELC_DBG(DEBUG, "scope '%s' %d not used", conf_scope->id, conf_scope->event); + else if (flt_otel_scope_run(s, f, chn, conf_scope, &ts_steady, &ts_system, flt_otel_event_data[event].smp_opt_dir, err) == FLT_OTEL_RET_ERROR) + retval = FLT_OTEL_RET_ERROR; + } + + OTELC_DBG(DEBUG, "event = %d %s, chn = %p, s->req = %p, s->res = %p", event, flt_otel_event_data[event].an_name, chn, &(s->req), &(s->res)); + + OTELC_RETURN_INT(retval); +} + /* * Local variables: * c-indent-level: 8 diff --git a/addons/otel/src/filter.c b/addons/otel/src/filter.c index 1d0099621..726a788f5 100644 --- a/addons/otel/src/filter.c +++ b/addons/otel/src/filter.c @@ -11,6 +11,194 @@ const char *otel_flt_id = "the OpenTelemetry filter"; +/*** + * NAME + * flt_otel_lib_init - OTel library initialization + * + * SYNOPSIS + * static int flt_otel_lib_init(struct flt_otel_conf_instr *instr, char **err) + * + * ARGUMENTS + * instr - pointer to the instrumentation configuration + * err - indirect pointer to error message string + * + * DESCRIPTION + * Initializes the OpenTelemetry C wrapper library for the instrumentation + * specified by . It verifies the library version, constructs the + * absolute configuration path from ->config, calls otelc_init(), and + * creates the tracer and meter instances. On success, it registers the + * memory and thread ID callbacks via otelc_ext_init(). + * + * RETURN VALUE + * Returns 0 on success, or FLT_OTEL_RET_ERROR on failure. + */ +static int flt_otel_lib_init(struct flt_otel_conf_instr *instr, char **err) +{ + char cwd[PATH_MAX], path[PATH_MAX]; + int rc, retval = FLT_OTEL_RET_ERROR; + + OTELC_FUNC("%p, %p:%p", instr, OTELC_DPTR_ARGS(err)); + + if (flt_otel_pool_init() == FLT_OTEL_RET_ERROR) { + FLT_OTEL_ERR("failed to initialize memory pools"); + + OTELC_RETURN_INT(retval); + } + + flt_otel_pool_info(); + + if (getcwd(cwd, sizeof(cwd)) == NULL) { + FLT_OTEL_ERR("failed to get current working directory"); + + OTELC_RETURN_INT(retval); + } + + rc = snprintf(path, sizeof(path), "%s/%s", cwd, instr->config); + if ((rc == -1) || (rc >= sizeof(path))) { + FLT_OTEL_ERR("failed to construct the OpenTelemetry configuration path"); + + OTELC_RETURN_INT(retval); + } + + retval = 0; + + OTELC_RETURN_INT(retval); +} + + +/*** + * NAME + * flt_otel_is_disabled - filter disabled check + * + * SYNOPSIS + * bool flt_otel_is_disabled(const struct filter *f, int event) + * + * ARGUMENTS + * f - the filter instance to check + * event - the event identifier, or -1 (debug only) + * + * DESCRIPTION + * Checks whether the filter instance is disabled for the current stream by + * examining the runtime context's flag_disabled field. When DEBUG_OTEL is + * enabled, it also logs the filter name, type and the name. + * + * RETURN VALUE + * Returns true if the filter is disabled, false otherwise. + */ +bool flt_otel_is_disabled(const struct filter *f FLT_OTEL_DBG_ARGS(, int event)) +{ +#ifdef DEBUG_OTEL + const struct flt_otel_conf *conf = FLT_OTEL_CONF(f); + const char *msg; +#endif + bool retval; + + retval = FLT_OTEL_RT_CTX(f->ctx)->flag_disabled ? 1 : 0; + +#ifdef DEBUG_OTEL + msg = retval ? " (disabled)" : ""; + + if (OTELC_IN_RANGE(event, 0, FLT_OTEL_EVENT_MAX - 1)) + OTELC_DBG(NOTICE, "filter '%s', type: %s, event: '%s' %d%s", conf->id, flt_otel_type(f), flt_otel_event_data[event].name, event, msg); + else + OTELC_DBG(NOTICE, "filter '%s', type: %s%s", conf->id, flt_otel_type(f), msg); +#endif + + return retval; +} + + +/*** + * NAME + * flt_otel_return_int - error handler for int-returning callbacks + * + * SYNOPSIS + * static int flt_otel_return_int(const struct filter *f, char **err, int retval) + * + * ARGUMENTS + * f - the filter instance + * err - indirect pointer to error message string + * retval - the return value from the caller + * + * DESCRIPTION + * Error handler for filter callbacks that return an integer value. If + * indicates an error or contains a message, the filter is + * disabled when hard-error mode is enabled; in soft-error mode, the error + * is silently cleared. The error message is always freed before returning. + * + * RETURN VALUE + * Returns FLT_OTEL_RET_OK if an error was handled, or the original . + */ +static int flt_otel_return_int(const struct filter *f, char **err, int retval) +{ + struct flt_otel_runtime_context *rt_ctx = f->ctx; + + /* Disable the filter on hard errors; ignore on soft errors. */ + if ((retval == FLT_OTEL_RET_ERROR) || ((err != NULL) && (*err != NULL))) { + if (rt_ctx->flag_harderr) { + OTELC_DBG(INFO, "WARNING: filter hard-error (disabled)"); + + rt_ctx->flag_disabled = 1; + +#ifdef FLT_OTEL_USE_COUNTERS + _HA_ATOMIC_ADD(FLT_OTEL_CONF(f)->cnt.disabled + 1, 1); +#endif + } else { + OTELC_DBG(INFO, "WARNING: filter soft-error"); + } + + retval = FLT_OTEL_RET_OK; + } + + FLT_OTEL_ERR_FREE(*err); + + return retval; +} + + +/*** + * NAME + * flt_otel_return_void - error handler for void-returning callbacks + * + * SYNOPSIS + * static void flt_otel_return_void(const struct filter *f, char **err) + * + * ARGUMENTS + * f - the filter instance + * err - indirect pointer to error message string + * + * DESCRIPTION + * Error handler for filter callbacks that return void. If contains + * a message, the filter is disabled when hard-error mode is enabled; in + * soft-error mode, the error is silently cleared. The error message is + * always freed before returning. + * + * RETURN VALUE + * This function does not return a value. + */ +static void flt_otel_return_void(const struct filter *f, char **err) +{ + struct flt_otel_runtime_context *rt_ctx = f->ctx; + + /* Disable the filter on hard errors; ignore on soft errors. */ + if ((err != NULL) && (*err != NULL)) { + if (rt_ctx->flag_harderr) { + OTELC_DBG(INFO, "WARNING: filter hard-error (disabled)"); + + rt_ctx->flag_disabled = 1; + +#ifdef FLT_OTEL_USE_COUNTERS + _HA_ATOMIC_ADD(FLT_OTEL_CONF(f)->cnt.disabled + 1, 1); +#endif + } else { + OTELC_DBG(INFO, "WARNING: filter soft-error"); + } + } + + FLT_OTEL_ERR_FREE(*err); +} + + /*** * NAME * flt_otel_ops_init - filter init callback (flt_ops.init) @@ -31,9 +219,28 @@ const char *otel_flt_id = "the OpenTelemetry filter"; */ static int flt_otel_ops_init(struct proxy *p, struct flt_conf *fconf) { + struct flt_otel_conf *conf = FLT_OTEL_DEREF(fconf, conf, NULL); + char *err = NULL; + int retval = FLT_OTEL_RET_ERROR; + OTELC_FUNC("%p, %p", p, fconf); - OTELC_RETURN_INT(0); + if (conf == NULL) + OTELC_RETURN_INT(retval); + + /* + * Initialize the OpenTelemetry library. + */ + retval = flt_otel_lib_init(conf->instr, &err); + if (retval != FLT_OTEL_RET_ERROR) + /* Do nothing. */; + else if (err != NULL) { + FLT_OTEL_ALERT("%s", err); + + FLT_OTEL_ERR_FREE(err); + } + + OTELC_RETURN_INT(retval); } @@ -58,8 +265,22 @@ static int flt_otel_ops_init(struct proxy *p, struct flt_conf *fconf) */ static void flt_otel_ops_deinit(struct proxy *p, struct flt_conf *fconf) { + struct flt_otel_conf **conf = (fconf == NULL) ? NULL : (typeof(conf))&(fconf->conf); + OTELC_FUNC("%p, %p", p, fconf); + if (conf == NULL) + OTELC_RETURN(); + +#ifdef DEBUG_OTEL +# ifdef FLT_OTEL_USE_COUNTERS + OTELC_DBG(LOG, "attach counters: %" PRIu64 " %" PRIu64 " %" PRIu64 " %" PRIu64, (*conf)->cnt.attached[0], (*conf)->cnt.attached[1], (*conf)->cnt.attached[2], (*conf)->cnt.attached[3]); +# endif +#endif + + flt_otel_conf_free(conf); + flt_otel_pool_destroy(); + OTELC_RETURN(); } @@ -373,9 +594,28 @@ static int flt_otel_ops_check(struct proxy *p, struct flt_conf *fconf) */ static int flt_otel_ops_init_per_thread(struct proxy *p, struct flt_conf *fconf) { + struct flt_otel_conf *conf = FLT_OTEL_DEREF(fconf, conf, NULL); + int retval = FLT_OTEL_RET_ERROR; + OTELC_FUNC("%p, %p", p, fconf); - OTELC_RETURN_INT(FLT_OTEL_RET_OK); + if (conf == NULL) + OTELC_RETURN_INT(retval); + + /* + * Start the OpenTelemetry library tracer thread. Enable HTX streams + * filtering. + */ + if (!(fconf->flags & FLT_CFG_FL_HTX)) { + retval = FLT_OTEL_RET_OK; + + if (retval != FLT_OTEL_RET_ERROR) + fconf->flags |= FLT_CFG_FL_HTX; + } else { + retval = FLT_OTEL_RET_OK; + } + + OTELC_RETURN_INT(retval); } @@ -433,8 +673,63 @@ static void flt_otel_ops_deinit_per_thread(struct proxy *p, struct flt_conf *fco */ static int flt_otel_ops_attach(struct stream *s, struct filter *f) { + const struct flt_otel_conf *conf = FLT_OTEL_CONF(f); + char *err = NULL; + OTELC_FUNC("%p, %p", s, f); + /* Skip attachment when the filter is globally disabled. */ + if (_HA_ATOMIC_LOAD(&(conf->instr->flag_disabled))) { + OTELC_DBG(NOTICE, "filter '%s', type: %s (disabled)", conf->id, flt_otel_type(f)); + +#ifdef FLT_OTEL_USE_COUNTERS + _HA_ATOMIC_ADD(FLT_OTEL_CONF(f)->cnt.attached + 2, 1); +#endif + + OTELC_RETURN_INT(FLT_OTEL_RET_IGNORE); + } + else if (_HA_ATOMIC_LOAD(&(conf->instr->rate_limit)) < FLT_OTEL_FLOAT_U32(100.0)) { + uint32_t rnd = ha_random32(); + uint32_t rate = _HA_ATOMIC_LOAD(&(conf->instr->rate_limit)); + + if (rate <= rnd) { + OTELC_DBG(NOTICE, "filter '%s', type: %s (ignored: %u <= %u)", conf->id, flt_otel_type(f), rate, rnd); + +#ifdef FLT_OTEL_USE_COUNTERS + _HA_ATOMIC_ADD(FLT_OTEL_CONF(f)->cnt.attached + 1, 1); +#endif + + OTELC_RETURN_INT(FLT_OTEL_RET_IGNORE); + } + } + + OTELC_DBG(NOTICE, "filter '%s', type: %s (run)", conf->id, flt_otel_type(f)); + + /* Create the per-stream runtime context. */ + f->ctx = flt_otel_runtime_context_init(s, f, &err); + FLT_OTEL_ERR_FREE(err); + if (f->ctx == NULL) { + FLT_OTEL_LOG(LOG_EMERG, "failed to create context"); + +#ifdef FLT_OTEL_USE_COUNTERS + _HA_ATOMIC_ADD(FLT_OTEL_CONF(f)->cnt.attached + 3, 1); +#endif + + OTELC_RETURN_INT(FLT_OTEL_RET_IGNORE); + } + + /* + * AN_REQ_WAIT_HTTP and AN_RES_WAIT_HTTP analyzers can only be used + * in the .channel_post_analyze callback function. + */ + f->pre_analyzers |= conf->instr->analyzers & ((AN_REQ_ALL & ~AN_REQ_WAIT_HTTP & ~AN_REQ_HTTP_TARPIT) | (AN_RES_ALL & ~AN_RES_WAIT_HTTP)); + f->post_analyzers |= conf->instr->analyzers & (AN_REQ_WAIT_HTTP | AN_RES_WAIT_HTTP); + +#ifdef FLT_OTEL_USE_COUNTERS + _HA_ATOMIC_ADD(FLT_OTEL_CONF(f)->cnt.attached + 0, 1); +#endif + FLT_OTEL_LOG(LOG_INFO, "%08x %08x", f->pre_analyzers, f->post_analyzers); + OTELC_RETURN_INT(FLT_OTEL_RET_OK); } @@ -462,9 +757,33 @@ static int flt_otel_ops_attach(struct stream *s, struct filter *f) */ static int flt_otel_ops_stream_start(struct stream *s, struct filter *f) { + const struct flt_otel_conf *conf = FLT_OTEL_CONF(f); + struct flt_otel_runtime_context *rt_ctx; + char *err = NULL; + int retval = FLT_OTEL_RET_OK; + OTELC_FUNC("%p, %p", s, f); - OTELC_RETURN_INT(FLT_OTEL_RET_OK); + if (flt_otel_is_disabled(f FLT_OTEL_DBG_ARGS(, FLT_OTEL_EVENT__STREAM_START))) + OTELC_RETURN_INT(retval); + + /* The result of the function is ignored. */ + (void)flt_otel_event_run(s, f, NULL, FLT_OTEL_EVENT__STREAM_START, &err); + + /* + * Initialize the idle timer from the precomputed minimum idle_timeout + * in the instrumentation configuration. + */ + if (conf->instr->idle_timeout != 0) { + rt_ctx = FLT_OTEL_RT_CTX(f->ctx); + + rt_ctx->idle_timeout = conf->instr->idle_timeout; + rt_ctx->idle_exp = tick_add(now_ms, rt_ctx->idle_timeout); + + s->req.analyse_exp = tick_first(s->req.analyse_exp, rt_ctx->idle_exp); + } + + OTELC_RETURN_INT(flt_otel_return_int(f, &err, retval)); } @@ -491,9 +810,19 @@ static int flt_otel_ops_stream_start(struct stream *s, struct filter *f) */ static int flt_otel_ops_stream_set_backend(struct stream *s, struct filter *f, struct proxy *be) { + char *err = NULL; + int retval = FLT_OTEL_RET_OK; + OTELC_FUNC("%p, %p, %p", s, f, be); - OTELC_RETURN_INT(FLT_OTEL_RET_OK); + if (flt_otel_is_disabled(f FLT_OTEL_DBG_ARGS(, FLT_OTEL_EVENT__BACKEND_SET))) + OTELC_RETURN_INT(retval); + + OTELC_DBG(DEBUG, "backend: %s", be->id); + + (void)flt_otel_event_run(s, f, &(s->req), FLT_OTEL_EVENT__BACKEND_SET, &err); + + OTELC_RETURN_INT(flt_otel_return_int(f, &err, retval)); } @@ -517,8 +846,18 @@ static int flt_otel_ops_stream_set_backend(struct stream *s, struct filter *f, s */ static void flt_otel_ops_stream_stop(struct stream *s, struct filter *f) { + char *err = NULL; + OTELC_FUNC("%p, %p", s, f); + if (flt_otel_is_disabled(f FLT_OTEL_DBG_ARGS(, FLT_OTEL_EVENT__STREAM_STOP))) + OTELC_RETURN(); + + /* The result of the function is ignored. */ + (void)flt_otel_event_run(s, f, NULL, FLT_OTEL_EVENT__STREAM_STOP, &err); + + flt_otel_return_void(f, &err); + OTELC_RETURN(); } @@ -547,6 +886,10 @@ static void flt_otel_ops_detach(struct stream *s, struct filter *f) { OTELC_FUNC("%p, %p", s, f); + OTELC_DBG(NOTICE, "filter '%s', type: %s", FLT_OTEL_CONF(f)->id, flt_otel_type(f)); + + flt_otel_runtime_context_free(f); + OTELC_RETURN(); } @@ -574,8 +917,46 @@ static void flt_otel_ops_detach(struct stream *s, struct filter *f) */ static void flt_otel_ops_check_timeouts(struct stream *s, struct filter *f) { + struct flt_otel_runtime_context *rt_ctx; + char *err = NULL; + OTELC_FUNC("%p, %p", s, f); + if (flt_otel_is_disabled(f FLT_OTEL_DBG_ARGS(, -1))) + OTELC_RETURN(); + + rt_ctx = FLT_OTEL_RT_CTX(f->ctx); + + /* + * This callback is invoked for every timer event on the stream, + * not only for our idle timer. The filter API provides no way to + * distinguish which timer expired, so the tick check below is the only + * mechanism to determine whether our idle timer is the one that fired. + */ + if (tick_isset(rt_ctx->idle_exp) && tick_is_expired(rt_ctx->idle_exp, now_ms)) { + /* Fire the on-idle-timeout event. */ + (void)flt_otel_event_run(s, f, &(s->req), FLT_OTEL_EVENT__IDLE_TIMEOUT, &err); + + /* Reschedule the next idle timeout. */ + rt_ctx->idle_exp = tick_add(now_ms, rt_ctx->idle_timeout); + + /* + * Reset analyse_exp if it has expired before merging in the new + * idle tick. Without this, tick_first() would keep returning + * the stale expired value, causing the stream task to wake in + * a tight loop. + */ + if (tick_is_expired(s->req.analyse_exp, now_ms)) + s->req.analyse_exp = TICK_ETERNITY; + + s->req.analyse_exp = tick_first(s->req.analyse_exp, rt_ctx->idle_exp); + + /* Force the request and response analysers to be re-evaluated. */ + s->pending_events |= STRM_EVT_MSG; + } + + flt_otel_return_void(f, &err); + OTELC_RETURN(); } @@ -603,9 +984,85 @@ static void flt_otel_ops_check_timeouts(struct stream *s, struct filter *f) */ static int flt_otel_ops_channel_start_analyze(struct stream *s, struct filter *f, struct channel *chn) { + char *err = NULL; + int retval; + OTELC_FUNC("%p, %p, %p", s, f, chn); - OTELC_RETURN_INT(FLT_OTEL_RET_OK); + if (flt_otel_is_disabled(f FLT_OTEL_DBG_ARGS(, (chn->flags & CF_ISRESP) ? FLT_OTEL_EVENT_RES_SERVER_SESS_START : FLT_OTEL_EVENT_REQ_CLIENT_SESS_START))) + OTELC_RETURN_INT(FLT_OTEL_RET_OK); + + OTELC_DBG(DEBUG, "channel: %s, mode: %s (%s)", flt_otel_chn_label(chn), flt_otel_pr_mode(s), flt_otel_stream_pos(s)); + + if (chn->flags & CF_ISRESP) { + /* The response channel. */ + chn->analysers |= f->pre_analyzers & AN_RES_ALL; + + /* The event 'on-server-session-start'. */ + retval = flt_otel_event_run(s, f, chn, FLT_OTEL_EVENT_RES_SERVER_SESS_START, &err); + + /* + * WAIT is currently never returned by flt_otel_event_run(), + * this is kept for defensive purposes only. + */ + if (retval == FLT_OTEL_RET_WAIT) { + channel_dont_read(chn); + channel_dont_close(chn); + } + } else { + /* The request channel. */ + chn->analysers |= f->pre_analyzers & AN_REQ_ALL; + + /* The event 'on-client-session-start'. */ + retval = flt_otel_event_run(s, f, chn, FLT_OTEL_EVENT_REQ_CLIENT_SESS_START, &err); + } + + /* + * Data filter registration is intentionally disabled. The http_payload + * and tcp_payload callbacks are debug-only stubs (registered via + * OTELC_DBG_IFDEF) and do not process data. + * + * register_data_filter(s, chn, f); + */ + + /* + * Propagate the idle-timeout expiry to the channel so the stream task + * keeps waking at the configured interval. + */ + if (tick_isset(FLT_OTEL_RT_CTX(f->ctx)->idle_exp)) + chn->analyse_exp = tick_first(chn->analyse_exp, FLT_OTEL_RT_CTX(f->ctx)->idle_exp); + + OTELC_RETURN_INT(flt_otel_return_int(f, &err, retval)); +} + + +/*** + * NAME + * flt_otel_get_event - look up an event index by analyzer bit + * + * SYNOPSIS + * static int flt_otel_get_event(uint an_bit) + * + * ARGUMENTS + * an_bit - analyzer bit to search for + * + * DESCRIPTION + * Searches the flt_otel_event_data table for the entry whose an_bit field + * matches . + * + * RETURN VALUE + * Returns the table index on success, FLT_OTEL_RET_ERROR if no match is + * found. + */ +static int flt_otel_get_event(uint an_bit) +{ + int i; + + for (i = 0; i < OTELC_TABLESIZE(flt_otel_event_data); i++) + if (flt_otel_event_data[i].an_bit == an_bit) + return i; + + return FLT_OTEL_RET_ERROR; } @@ -632,9 +1089,31 @@ static int flt_otel_ops_channel_start_analyze(struct stream *s, struct filter *f */ static int flt_otel_ops_channel_pre_analyze(struct stream *s, struct filter *f, struct channel *chn, uint an_bit) { + char *err = NULL; + int event, retval; + OTELC_FUNC("%p, %p, %p, 0x%08x", s, f, chn, an_bit); - OTELC_RETURN_INT(FLT_OTEL_RET_OK); + event = flt_otel_get_event(an_bit); + if (event == FLT_OTEL_RET_ERROR) + OTELC_RETURN_INT(FLT_OTEL_RET_OK); + else if (flt_otel_is_disabled(f FLT_OTEL_DBG_ARGS(, event))) + OTELC_RETURN_INT(FLT_OTEL_RET_OK); + + OTELC_DBG(DEBUG, "channel: %s, mode: %s (%s), analyzer: %s", flt_otel_chn_label(chn), flt_otel_pr_mode(s), flt_otel_stream_pos(s), flt_otel_analyzer(an_bit)); + + retval = flt_otel_event_run(s, f, chn, event, &err); + + /* + * WAIT is currently never returned by flt_otel_event_run(), this is + * kept for defensive purposes only. + */ + if ((retval == FLT_OTEL_RET_WAIT) && (chn->flags & CF_ISRESP)) { + channel_dont_read(chn); + channel_dont_close(chn); + } + + OTELC_RETURN_INT(flt_otel_return_int(f, &err, retval)); } @@ -662,9 +1141,22 @@ static int flt_otel_ops_channel_pre_analyze(struct stream *s, struct filter *f, */ static int flt_otel_ops_channel_post_analyze(struct stream *s, struct filter *f, struct channel *chn, uint an_bit) { + char *err = NULL; + int event, retval; + OTELC_FUNC("%p, %p, %p, 0x%08x", s, f, chn, an_bit); - OTELC_RETURN_INT(FLT_OTEL_RET_OK); + event = flt_otel_get_event(an_bit); + if (event == FLT_OTEL_RET_ERROR) + OTELC_RETURN_INT(FLT_OTEL_RET_OK); + else if (flt_otel_is_disabled(f FLT_OTEL_DBG_ARGS(, event))) + OTELC_RETURN_INT(FLT_OTEL_RET_OK); + + OTELC_DBG(DEBUG, "channel: %s, mode: %s (%s), analyzer: %s", flt_otel_chn_label(chn), flt_otel_pr_mode(s), flt_otel_stream_pos(s), flt_otel_analyzer(an_bit)); + + retval = flt_otel_event_run(s, f, chn, event, &err); + + OTELC_RETURN_INT(flt_otel_return_int(f, &err, retval)); } @@ -692,9 +1184,35 @@ static int flt_otel_ops_channel_post_analyze(struct stream *s, struct filter *f, */ static int flt_otel_ops_channel_end_analyze(struct stream *s, struct filter *f, struct channel *chn) { + char *err = NULL; + int rc, retval; + OTELC_FUNC("%p, %p, %p", s, f, chn); - OTELC_RETURN_INT(FLT_OTEL_RET_OK); + if (flt_otel_is_disabled(f FLT_OTEL_DBG_ARGS(, (chn->flags & CF_ISRESP) ? FLT_OTEL_EVENT_RES_SERVER_SESS_END : FLT_OTEL_EVENT_REQ_CLIENT_SESS_END))) + OTELC_RETURN_INT(FLT_OTEL_RET_OK); + + OTELC_DBG(DEBUG, "channel: %s, mode: %s (%s)", flt_otel_chn_label(chn), flt_otel_pr_mode(s), flt_otel_stream_pos(s)); + + if (chn->flags & CF_ISRESP) { + /* The response channel, event 'on-server-session-end'. */ + retval = flt_otel_event_run(s, f, chn, FLT_OTEL_EVENT_RES_SERVER_SESS_END, &err); + } else { + /* The request channel, event 'on-client-session-end'. */ + retval = flt_otel_event_run(s, f, chn, FLT_OTEL_EVENT_REQ_CLIENT_SESS_END, &err); + + /* + * In case an event using server response is defined and not + * executed, event 'on-server-unavailable' is called here. + */ + if ((FLT_OTEL_CONF(f)->instr->analyzers & AN_RES_ALL) && !(FLT_OTEL_RT_CTX(f->ctx)->analyzers & AN_RES_ALL)) { + rc = flt_otel_event_run(s, f, chn, FLT_OTEL_EVENT_REQ_SERVER_UNAVAILABLE, &err); + if ((retval == FLT_OTEL_RET_OK) && (rc != FLT_OTEL_RET_OK)) + retval = rc; + } + } + + OTELC_RETURN_INT(flt_otel_return_int(f, &err, retval)); } @@ -720,9 +1238,20 @@ static int flt_otel_ops_channel_end_analyze(struct stream *s, struct filter *f, */ static int flt_otel_ops_http_headers(struct stream *s, struct filter *f, struct http_msg *msg) { + int event = (msg->chn->flags & CF_ISRESP) ? FLT_OTEL_EVENT_RES_HTTP_HEADERS : FLT_OTEL_EVENT_REQ_HTTP_HEADERS; + char *err = NULL; + int retval = FLT_OTEL_RET_OK; + OTELC_FUNC("%p, %p, %p", s, f, msg); - OTELC_RETURN_INT(FLT_OTEL_RET_OK); + if (flt_otel_is_disabled(f FLT_OTEL_DBG_ARGS(, event))) + OTELC_RETURN_INT(retval); + + OTELC_DBG(DEBUG, "channel: %s, mode: %s (%s)", flt_otel_chn_label(msg->chn), flt_otel_pr_mode(s), flt_otel_stream_pos(s)); + + (void)flt_otel_event_run(s, f, msg->chn, event, &err); + + OTELC_RETURN_INT(flt_otel_return_int(f, &err, retval)); } @@ -751,9 +1280,21 @@ static int flt_otel_ops_http_headers(struct stream *s, struct filter *f, struct */ static int flt_otel_ops_http_payload(struct stream *s, struct filter *f, struct http_msg *msg, uint offset, uint len) { + char *err = NULL; + int retval = len; + OTELC_FUNC("%p, %p, %p, %u, %u", s, f, msg, offset, len); - OTELC_RETURN_INT(len); + if (flt_otel_is_disabled(f FLT_OTEL_DBG_ARGS(, -1))) + OTELC_RETURN_INT(len); + + OTELC_DBG(DEBUG, "channel: %s, mode: %s (%s), offset: %u, len: %u, forward: %d", flt_otel_chn_label(msg->chn), flt_otel_pr_mode(s), flt_otel_stream_pos(s), offset, len, retval); + + /* Debug stub -- retval is always len, wakeup is never reached. */ + if (retval != len) + task_wakeup(s->task, TASK_WOKEN_MSG); + + OTELC_RETURN_INT(flt_otel_return_int(f, &err, retval)); } #endif /* DEBUG_OTEL */ @@ -781,9 +1322,20 @@ static int flt_otel_ops_http_payload(struct stream *s, struct filter *f, struct */ static int flt_otel_ops_http_end(struct stream *s, struct filter *f, struct http_msg *msg) { + int event = (msg->chn->flags & CF_ISRESP) ? FLT_OTEL_EVENT_RES_HTTP_END : FLT_OTEL_EVENT_REQ_HTTP_END; + char *err = NULL; + int retval = FLT_OTEL_RET_OK; + OTELC_FUNC("%p, %p, %p", s, f, msg); - OTELC_RETURN_INT(FLT_OTEL_RET_OK); + if (flt_otel_is_disabled(f FLT_OTEL_DBG_ARGS(, event))) + OTELC_RETURN_INT(retval); + + OTELC_DBG(DEBUG, "channel: %s, mode: %s (%s)", flt_otel_chn_label(msg->chn), flt_otel_pr_mode(s), flt_otel_stream_pos(s)); + + (void)flt_otel_event_run(s, f, msg->chn, event, &err); + + OTELC_RETURN_INT(flt_otel_return_int(f, &err, retval)); } @@ -809,8 +1361,19 @@ static int flt_otel_ops_http_end(struct stream *s, struct filter *f, struct http */ static void flt_otel_ops_http_reply(struct stream *s, struct filter *f, short status, const struct buffer *msg) { + char *err = NULL; + OTELC_FUNC("%p, %p, %hd, %p", s, f, status, msg); + if (flt_otel_is_disabled(f FLT_OTEL_DBG_ARGS(, FLT_OTEL_EVENT_RES_HTTP_REPLY))) + OTELC_RETURN(); + + OTELC_DBG(DEBUG, "channel: -, mode: %s (%s), status: %hd", flt_otel_pr_mode(s), flt_otel_stream_pos(s), status); + + (void)flt_otel_event_run(s, f, &(s->res), FLT_OTEL_EVENT_RES_HTTP_REPLY, &err); + + flt_otel_return_void(f, &err); + OTELC_RETURN(); } @@ -838,8 +1401,17 @@ static void flt_otel_ops_http_reply(struct stream *s, struct filter *f, short st */ static void flt_otel_ops_http_reset(struct stream *s, struct filter *f, struct http_msg *msg) { + char *err = NULL; + OTELC_FUNC("%p, %p, %p", s, f, msg); + if (flt_otel_is_disabled(f FLT_OTEL_DBG_ARGS(, -1))) + OTELC_RETURN(); + + OTELC_DBG(DEBUG, "channel: %s, mode: %s (%s)", flt_otel_chn_label(msg->chn), flt_otel_pr_mode(s), flt_otel_stream_pos(s)); + + flt_otel_return_void(f, &err); + OTELC_RETURN(); } @@ -867,9 +1439,26 @@ static void flt_otel_ops_http_reset(struct stream *s, struct filter *f, struct h */ static int flt_otel_ops_tcp_payload(struct stream *s, struct filter *f, struct channel *chn, uint offset, uint len) { + char *err = NULL; + int retval = len; + OTELC_FUNC("%p, %p, %p, %u, %u", s, f, chn, offset, len); - OTELC_RETURN_INT(len); + if (flt_otel_is_disabled(f FLT_OTEL_DBG_ARGS(, -1))) + OTELC_RETURN_INT(len); + + OTELC_DBG(DEBUG, "channel: %s, mode: %s (%s), offset: %u, len: %u, forward: %d", flt_otel_chn_label(chn), flt_otel_pr_mode(s), flt_otel_stream_pos(s), offset, len, retval); + + /* Debug stub -- no data processing implemented yet. */ + if (s->flags & SF_HTX) { + } else { + } + + /* Debug stub -- retval is always len, wakeup is never reached. */ + if (retval != len) + task_wakeup(s->task, TASK_WOKEN_MSG); + + OTELC_RETURN_INT(flt_otel_return_int(f, &err, retval)); } #endif /* DEBUG_OTEL */ diff --git a/addons/otel/src/util.c b/addons/otel/src/util.c index a951efb4a..6c0913f06 100644 --- a/addons/otel/src/util.c +++ b/addons/otel/src/util.c @@ -38,6 +38,140 @@ void flt_otel_args_dump(const char **args) } +/*** + * NAME + * flt_otel_chn_label - channel direction label + * + * SYNOPSIS + * const char *flt_otel_chn_label(const struct channel *chn) + * + * ARGUMENTS + * chn - channel to identify + * + * DESCRIPTION + * Returns a human-readable label indicating the channel direction based on + * the CF_ISRESP flag. + * + * RETURN VALUE + * Returns "RESponse" for response channels, or "REQuest" for request + * channels. + */ +const char *flt_otel_chn_label(const struct channel *chn) +{ + return (chn == NULL) ? "-" : ((chn->flags & CF_ISRESP) ? "RESponse" : "REQuest"); +} + + +/*** + * NAME + * flt_otel_pr_mode - proxy mode label + * + * SYNOPSIS + * const char *flt_otel_pr_mode(const struct stream *s) + * + * ARGUMENTS + * s - stream to check + * + * DESCRIPTION + * Returns a human-readable label indicating the proxy mode. Uses the + * backend proxy if a backend is assigned, otherwise the frontend proxy. + * + * RETURN VALUE + * Returns "HTTP" for HTTP mode proxies, or "TCP" for TCP mode proxies. + */ +const char *flt_otel_pr_mode(const struct stream *s) +{ + struct proxy *px = (s->flags & SF_BE_ASSIGNED) ? s->be : strm_fe(s); + + return (px->mode == PR_MODE_HTTP) ? "HTTP" : "TCP"; +} + + +/*** + * NAME + * flt_otel_stream_pos - stream position label + * + * SYNOPSIS + * const char *flt_otel_stream_pos(const struct stream *s) + * + * ARGUMENTS + * s - stream to check + * + * DESCRIPTION + * Returns a human-readable label indicating the stream position based on the + * SF_BE_ASSIGNED flag. + * + * RETURN VALUE + * Returns "backend" if a backend is assigned, or "frontend" otherwise. + */ +const char *flt_otel_stream_pos(const struct stream *s) +{ + return (s->flags & SF_BE_ASSIGNED) ? "backend" : "frontend"; +} + + +/*** + * NAME + * flt_otel_type - filter type label + * + * SYNOPSIS + * const char *flt_otel_type(const struct filter *f) + * + * ARGUMENTS + * f - filter instance to check + * + * DESCRIPTION + * Returns a human-readable label indicating the filter type based on the + * FLT_FL_IS_BACKEND_FILTER flag. + * + * RETURN VALUE + * Returns "backend" for backend filters, or "frontend" for frontend filters. + */ +const char *flt_otel_type(const struct filter *f) +{ + return (f->flags & FLT_FL_IS_BACKEND_FILTER) ? "backend" : "frontend"; +} + + +/*** + * NAME + * flt_otel_analyzer - analyzer bit name lookup + * + * SYNOPSIS + * const char *flt_otel_analyzer(uint an_bit) + * + * ARGUMENTS + * an_bit - the analyzer identifier bit + * + * DESCRIPTION + * Looks up the human-readable analyzer name for the given value from + * the flt_otel_event_data table. If the bit is not found, a formatted error + * string is returned from a thread-local buffer. + * + * RETURN VALUE + * Returns the analyzer name string, or a formatted error message if the bit + * is invalid. + */ +const char *flt_otel_analyzer(uint an_bit) +{ + static THREAD_LOCAL char retbuf[32]; + const char *retptr = NULL; + int i; + + for (i = 0; i < OTELC_TABLESIZE(flt_otel_event_data); i++) + if (flt_otel_event_data[i].an_bit == an_bit) { + retptr = flt_otel_event_data[i].an_name; + + break; + } + + if (retptr == NULL) + (void)snprintf(retbuf, sizeof(retbuf), "invalid an_bit: 0x%08x", an_bit); + + return (retptr == NULL) ? retbuf : retptr; +} + + /*** * NAME * flt_otel_list_dump - debug list summary