diff --git a/addons/otel/include/conf.h b/addons/otel/include/conf.h index e0ce4c081..6986e0da3 100644 --- a/addons/otel/include/conf.h +++ b/addons/otel/include/conf.h @@ -197,6 +197,16 @@ struct flt_otel_conf_instr { struct list ph_scopes; /* List of all used scopes. */ }; +/* Runtime counters for filter diagnostics. */ +struct flt_otel_counters { +#ifdef DEBUG_OTEL + struct { + bool flag_used; /* Whether this event is used. */ + uint64_t htx[2]; /* htx_is_empty() function result counter. */ + } event[FLT_OTEL_EVENT_MAX]; +#endif +}; + /* The OpenTelemetry filter configuration. */ struct flt_otel_conf { struct proxy *proxy; /* Proxy owning the filter. */ @@ -205,6 +215,7 @@ struct flt_otel_conf { struct flt_otel_conf_instr *instr; /* The OpenTelemetry instrumentation settings. */ struct list groups; /* List of all available groups. */ struct list scopes; /* List of all available scopes. */ + struct flt_otel_counters cnt; /* Various counters related to filter operation. */ struct list smp_args; /* Deferred OTEL sample fetch args to resolve. */ }; diff --git a/addons/otel/include/define.h b/addons/otel/include/define.h index 9e71df77e..4ca3c28ae 100644 --- a/addons/otel/include/define.h +++ b/addons/otel/include/define.h @@ -3,6 +3,9 @@ #ifndef _OTEL_DEFINE_H_ #define _OTEL_DEFINE_H_ +/* Safe pointer dereference with default value. */ +#define FLT_OTEL_DEREF(p,m,v) (((p) != NULL) ? (p)->m : (v)) + /* Check whether argument at index n is in range, non-NULL and non-empty. */ #define FLT_OTEL_ARG_ISVALID(n) ({ typeof(n) _n = (n); OTELC_IN_RANGE(_n, 0, MAX_LINE_ARGS - 1) && (args[_n] != NULL) && (*args[_n] != '\0'); }) diff --git a/addons/otel/include/filter.h b/addons/otel/include/filter.h index 713eea4d0..cb495f90d 100644 --- a/addons/otel/include/filter.h +++ b/addons/otel/include/filter.h @@ -5,6 +5,7 @@ #define FLT_OTEL_FMT_NAME "'" FLT_OTEL_OPT_NAME "' : " #define FLT_OTEL_FMT_TYPE "'filter' : " +#define FLT_OTEL_ALERT(f, ...) ha_alert(FLT_OTEL_FMT_TYPE FLT_OTEL_FMT_NAME f "\n", ##__VA_ARGS__) #define FLT_OTEL_CONDITION_IF "if" #define FLT_OTEL_CONDITION_UNLESS "unless" @@ -17,6 +18,21 @@ enum FLT_OTEL_RET_enum { FLT_OTEL_RET_OK = 1, }; +/* Dump or iterate a named configuration list for debugging. */ +#define FLT_OTEL_DBG_LIST(d,m,p,t,v,f) \ + do { \ + if (LIST_ISEMPTY(&((d)->m##s))) { \ + OTELC_DBG(DEBUG, p "- no " #m "s " t); \ + } else { \ + const struct flt_otel_conf_##m *v; \ + \ + OTELC_DBG(DEBUG, p "- " t " " #m "s: %s", \ + flt_otel_list_dump(&((d)->m##s))); \ + list_for_each_entry(v, &((d)->m##s), list) \ + do { f; } while (0); \ + } \ + } while (0) + extern const char *otel_flt_id; extern struct flt_ops flt_otel_ops; diff --git a/addons/otel/src/filter.c b/addons/otel/src/filter.c index b012f27c9..1d0099621 100644 --- a/addons/otel/src/filter.c +++ b/addons/otel/src/filter.c @@ -90,9 +90,265 @@ static void flt_otel_ops_deinit(struct proxy *p, struct flt_conf *fconf) */ static int flt_otel_ops_check(struct proxy *p, struct flt_conf *fconf) { + struct proxy *px; + struct flt_otel_conf *conf = FLT_OTEL_DEREF(fconf, conf, NULL); + struct flt_otel_conf_group *conf_group; + struct flt_otel_conf_scope *conf_scope; + struct flt_otel_conf_ph *ph_group, *ph_scope; + int retval = 0, scope_unused_cnt = 0, span_root_cnt = 0; + OTELC_FUNC("%p, %p", p, fconf); - OTELC_RETURN_INT(0); + if (conf == NULL) + OTELC_RETURN_INT(++retval); + + /* + * Resolve deferred OTEL sample fetch arguments. + * + * These were kept out of the proxy's arg list during parsing to avoid + * the global smp_resolve_args() call, which would reject backend-only + * fetches on a frontend proxy. All backends and servers are now + * available, so resolve under full FE+BE capabilities. + */ + if (!LIST_ISEMPTY(&(conf->smp_args))) { + char *err = NULL; + uint saved_cap = p->cap; + + LIST_SPLICE(&(p->conf.args.list), &(conf->smp_args)); + LIST_INIT(&(conf->smp_args)); + p->cap |= PR_CAP_LISTEN; + + if (smp_resolve_args(p, &err) != 0) { + FLT_OTEL_ALERT("%s", err); + ha_free(&err); + + retval++; + } + + p->cap = saved_cap; + } + + /* + * If only the proxy specified with the

parameter is checked, then + * no duplicate filters can be found that are not defined in the same + * configuration sections. + */ + for (px = proxies_list; px != NULL; px = px->next) { + struct flt_conf *fconf_tmp; + + OTELC_DBG(NOTICE, "proxy '%s'", px->id); + + /* + * The names of all OTEL filters (filter ID) should be checked, + * they must be unique. + */ + list_for_each_entry(fconf_tmp, &(px->filter_configs), list) + if ((fconf_tmp != fconf) && (fconf_tmp->id == otel_flt_id)) { + struct flt_otel_conf *conf_tmp = fconf_tmp->conf; + + OTELC_DBG(NOTICE, " OTEL filter '%s'", conf_tmp->id); + + if (strcmp(conf_tmp->id, conf->id) == 0) { + FLT_OTEL_ALERT("''%s' : duplicated filter ID'", conf_tmp->id); + + retval++; + } + } + } + + if (FLT_OTEL_DEREF(conf->instr, id, NULL) == NULL) { + FLT_OTEL_ALERT("''%s' : no instrumentation found'", conf->id); + + retval++; + } + + if ((conf->instr != NULL) && (conf->instr->config == NULL)) { + FLT_OTEL_ALERT("''%s' : no configuration file specified'", conf->instr->id); + + retval++; + } + + /* + * Checking that defined 'otel-group' section names are unique. + */ + list_for_each_entry(conf_group, &(conf->groups), list) { + struct flt_otel_conf_group *conf_group_tmp; + + list_for_each_entry(conf_group_tmp, &(conf->groups), list) { + if ((conf_group_tmp != conf_group) && (strcmp(conf_group_tmp->id, conf_group->id) == 0)) { + FLT_OTEL_ALERT("''%s' : duplicated " FLT_OTEL_PARSE_SECTION_GROUP_ID " '%s''", conf->id, conf_group->id); + + retval++; + + break; + } + } + } + + /* + * Checking that defined 'otel-scope' section names are unique. + */ + list_for_each_entry(conf_scope, &(conf->scopes), list) { + struct flt_otel_conf_scope *conf_scope_tmp; + + list_for_each_entry(conf_scope_tmp, &(conf->scopes), list) { + if ((conf_scope_tmp != conf_scope) && (strcmp(conf_scope_tmp->id, conf_scope->id) == 0)) { + FLT_OTEL_ALERT("''%s' : duplicated " FLT_OTEL_PARSE_SECTION_SCOPE_ID " '%s''", conf->id, conf_scope->id); + + retval++; + + break; + } + } + } + + /* + * Checking that defined 'otel-group' sections are not empty. + */ + list_for_each_entry(conf_group, &(conf->groups), list) + if (LIST_ISEMPTY(&(conf_group->ph_scopes))) + FLT_OTEL_ALERT("''%s' : " FLT_OTEL_PARSE_SECTION_GROUP_ID " '%s' has no scopes'", conf->id, conf_group->id); + + /* + * Checking that all defined 'otel-group' sections have correctly declared + * 'otel-scope' sections (ie whether the declared 'otel-scope' sections have + * corresponding definitions). + */ + list_for_each_entry(conf_group, &(conf->groups), list) + list_for_each_entry(ph_scope, &(conf_group->ph_scopes), list) { + bool flag_found = 0; + + list_for_each_entry(conf_scope, &(conf->scopes), list) + if (strcmp(ph_scope->id, conf_scope->id) == 0) { + ph_scope->ptr = conf_scope; + conf_scope->flag_used = 1; + flag_found = 1; + + break; + } + + if (!flag_found) { + FLT_OTEL_ALERT("'" FLT_OTEL_PARSE_SECTION_GROUP_ID " '%s' : references undefined " FLT_OTEL_PARSE_SECTION_SCOPE_ID " '%s''", conf_group->id, ph_scope->id); + + retval++; + } + } + + if (conf->instr != NULL) { + /* + * Checking that all declared 'groups' keywords have correctly + * defined 'otel-group' sections. + */ + list_for_each_entry(ph_group, &(conf->instr->ph_groups), list) { + bool flag_found = 0; + + list_for_each_entry(conf_group, &(conf->groups), list) + if (strcmp(ph_group->id, conf_group->id) == 0) { + ph_group->ptr = conf_group; + conf_group->flag_used = 1; + flag_found = 1; + + break; + } + + if (!flag_found) { + FLT_OTEL_ALERT("'" FLT_OTEL_PARSE_SECTION_INSTR_ID " '%s' : references undefined " FLT_OTEL_PARSE_SECTION_GROUP_ID " '%s''", conf->instr->id, ph_group->id); + + retval++; + } + } + + /* + * Checking that all declared 'scopes' keywords have correctly + * defined 'otel-scope' sections. + */ + list_for_each_entry(ph_scope, &(conf->instr->ph_scopes), list) { + bool flag_found = 0; + + list_for_each_entry(conf_scope, &(conf->scopes), list) + if (strcmp(ph_scope->id, conf_scope->id) == 0) { + ph_scope->ptr = conf_scope; + conf_scope->flag_used = 1; + flag_found = 1; + + break; + } + + if (!flag_found) { + FLT_OTEL_ALERT("'" FLT_OTEL_PARSE_SECTION_INSTR_ID " '%s' : references undefined " FLT_OTEL_PARSE_SECTION_SCOPE_ID " '%s''", conf->instr->id, ph_scope->id); + + retval++; + } + } + } + + OTELC_DBG(DEBUG, "--- filter '%s' configuration ----------", conf->id); + OTELC_DBG(DEBUG, "- defined spans ----------"); + + /* + * Walk every configured scope: for used ones, log the defined spans, + * count root spans, and set the required analyzer bits; for unused + * ones, record a warning so the operator is notified. + */ + list_for_each_entry(conf_scope, &(conf->scopes), list) { + if (conf_scope->flag_used) { + struct flt_otel_conf_span *conf_span; + + /* + * In principle, only one span should be labeled + * as a root span. + */ + list_for_each_entry(conf_span, &(conf_scope->spans), list) { + FLT_OTEL_DBG_CONF_SPAN(" ", conf_span); + + span_root_cnt += conf_span->flag_root ? 1 : 0; + } + +#ifdef DEBUG_OTEL + conf->cnt.event[conf_scope->event].flag_used = 1; +#endif + + /* Set the flags of the analyzers used. */ + conf->instr->analyzers |= flt_otel_event_data[conf_scope->event].an_bit; + + /* Track the minimum idle timeout. */ + if (conf_scope->event == FLT_OTEL_EVENT__IDLE_TIMEOUT) + if ((conf->instr->idle_timeout == 0) || (conf_scope->idle_timeout < conf->instr->idle_timeout)) + conf->instr->idle_timeout = conf_scope->idle_timeout; + } else { + FLT_OTEL_ALERT("''%s' : unused " FLT_OTEL_PARSE_SECTION_SCOPE_ID " '%s''", conf->id, conf_scope->id); + + scope_unused_cnt++; + } + } + + /* + * Unused scopes or a number of root spans other than one do not + * necessarily have to be errors, but it is good to print it when + * starting HAProxy. + */ + if (scope_unused_cnt > 0) + FLT_OTEL_ALERT("''%s' : %d scope(s) not in use'", conf->id, scope_unused_cnt); + + if (LIST_ISEMPTY(&(conf->scopes))) + /* Do nothing. */; + else if (span_root_cnt == 0) + FLT_OTEL_ALERT("''%s' : no span is marked as the root span'", conf->id); + else if (span_root_cnt > 1) + FLT_OTEL_ALERT("''%s' : multiple spans are marked as the root span'", conf->id); + + FLT_OTEL_DBG_LIST(conf, group, "", "defined", _group, + FLT_OTEL_DBG_CONF_GROUP(" ", _group); + FLT_OTEL_DBG_LIST(_group, ph_scope, " ", "used", _scope, FLT_OTEL_DBG_CONF_PH(" ", _scope))); + FLT_OTEL_DBG_LIST(conf, scope, "", "defined", _scope, FLT_OTEL_DBG_CONF_SCOPE(" ", _scope)); + + if (conf->instr != NULL) { + OTELC_DBG(DEBUG, " --- instrumentation '%s' configuration ----------", conf->instr->id); + FLT_OTEL_DBG_LIST(conf->instr, ph_group, " ", "used", _group, FLT_OTEL_DBG_CONF_PH(" ", _group)); + FLT_OTEL_DBG_LIST(conf->instr, ph_scope, " ", "used", _scope, FLT_OTEL_DBG_CONF_PH(" ", _scope)); + } + + OTELC_RETURN_INT(retval); }