mirror of
				https://github.com/asterisk/asterisk.git
				synced 2025-10-31 02:37:10 +00:00 
			
		
		
		
	These are the remaining issues found in /res. ASTERISK-29761 Change-Id: I572e6019c422780dde5ce8448b6c85c77af6046d
		
			
				
	
	
		
			376 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			376 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * Asterisk -- An open source telephony toolkit.
 | |
|  *
 | |
|  * Copyright (C) 2019 Sangoma, Inc.
 | |
|  *
 | |
|  * Matt Jordan <mjordan@digium.com>
 | |
|  *
 | |
|  * See http://www.asterisk.org for more information about
 | |
|  * the Asterisk project. Please do not directly contact
 | |
|  * any of the maintainers of this project for assistance;
 | |
|  * the project provides a web site, mailing lists and IRC
 | |
|  * channels for your use.
 | |
|  *
 | |
|  * This program is free software, distributed under the terms of
 | |
|  * the GNU General Public License Version 2. See the LICENSE file
 | |
|  * at the top of the source tree.
 | |
|  */
 | |
| 
 | |
| /*!
 | |
|  * \file
 | |
|  * \brief Prometheus PJSIP Outbound Registration Metrics
 | |
|  *
 | |
|  * \author Matt Jordan <mjordan@digium.com>
 | |
|  *
 | |
|  */
 | |
| 
 | |
| #include "asterisk.h"
 | |
| 
 | |
| #include "asterisk/stasis_message_router.h"
 | |
| #include "asterisk/stasis_system.h"
 | |
| #include "asterisk/res_prometheus.h"
 | |
| 
 | |
| #ifdef HAVE_PJPROJECT
 | |
| #include "asterisk/res_pjsip.h"
 | |
| #endif /* HAVE_PJPROJECT */
 | |
| 
 | |
| #include "prometheus_internal.h"
 | |
| 
 | |
| #ifdef HAVE_PJPROJECT
 | |
| 
 | |
| /*! \internal \brief Our one and only Stasis message router */
 | |
| static struct stasis_message_router *router;
 | |
| 
 | |
| /*!
 | |
|  * \internal
 | |
|  * \brief Wrapper object around our Metrics
 | |
|  *
 | |
|  * \details We keep a wrapper around the metric so we can easily
 | |
|  * update the value when the state of the registration changes, as
 | |
|  * well as remove and unregister the metric when someone destroys
 | |
|  * or reloads the registration
 | |
|  */
 | |
| struct prometheus_metric_wrapper {
 | |
| 	/*!
 | |
| 	 * \brief The actual metric. Worth noting that we do *NOT*
 | |
| 	 * own the metric, as it is registered with res_prometheus.
 | |
| 	 * Luckily, that module doesn't destroy metrics unless we
 | |
| 	 * tell it to or if the module unloads.
 | |
| 	 */
 | |
| 	struct prometheus_metric *metric;
 | |
| 	/*!
 | |
| 	 * \brief Unique key to look up the metric
 | |
| 	 */
 | |
| 	char key[128];
 | |
| };
 | |
| 
 | |
| /*!
 | |
|  * \internal Vector of metric wrappers
 | |
|  *
 | |
|  * \details
 | |
|  * Why a vector and not an ao2_container? Two reasons:
 | |
|  * (1) There's rarely a ton of outbound registrations, so an ao2_container
 | |
|  * is overkill when we can just walk a vector
 | |
|  * (2) The lifetime of wrappers is well contained
 | |
|  */
 | |
| static AST_VECTOR(, struct prometheus_metric_wrapper *) metrics;
 | |
| 
 | |
| AST_MUTEX_DEFINE_STATIC(metrics_lock);
 | |
| 
 | |
| /*!
 | |
|  * \internal
 | |
|  * \brief Create a wrapper for a metric given a key
 | |
|  *
 | |
|  * \param key The unique key
 | |
|  *
 | |
|  * \retval NULL on error
 | |
|  * \retval malloc'd metric wrapper on success
 | |
|  */
 | |
| static struct prometheus_metric_wrapper *create_wrapper(const char *key)
 | |
| {
 | |
| 	struct prometheus_metric_wrapper *wrapper;
 | |
| 
 | |
| 	wrapper = ast_calloc(1, sizeof(*wrapper));
 | |
| 	if (!wrapper) {
 | |
| 		return NULL;
 | |
| 	}
 | |
| 
 | |
| 	ast_copy_string(wrapper->key, key, sizeof(wrapper->key));
 | |
| 	return wrapper;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \internal
 | |
|  * \brief Get a wrapper by its key
 | |
|  *
 | |
|  * \param key The unqiue key for the wrapper
 | |
|  *
 | |
|  * \retval NULL on no wrapper found :-\
 | |
|  * \retval wrapper on success
 | |
|  */
 | |
| static struct prometheus_metric_wrapper *get_wrapper(const char *key)
 | |
| {
 | |
| 	int i;
 | |
| 	SCOPED_MUTEX(lock, &metrics_lock);
 | |
| 
 | |
| 	for (i = 0; i < AST_VECTOR_SIZE(&metrics); i++) {
 | |
| 		struct prometheus_metric_wrapper *wrapper = AST_VECTOR_GET(&metrics, i);
 | |
| 
 | |
| 		if (!strcmp(wrapper->key, key)) {
 | |
| 			return wrapper;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return NULL;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \internal
 | |
|  * \brief Convert an outbound registration state to a numeric value
 | |
|  *
 | |
|  * \param state The state to convert
 | |
|  *
 | |
|  * \retval int representation of the state
 | |
|  */
 | |
| static int registration_state_to_int(const char *state)
 | |
| {
 | |
| 	if (!strcasecmp(state, "Registered")) {
 | |
| 		return 1;
 | |
| 	} else if (!strcasecmp(state, "Rejected")) {
 | |
| 		return 2;
 | |
| 	}
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \internal
 | |
|  * \brief Sorcery observer callback called when a registration object is deleted
 | |
|  *
 | |
|  * \param obj The opaque object that was deleted
 | |
|  */
 | |
| static void registration_deleted_observer(const void *obj)
 | |
| {
 | |
| 	struct ast_variable *fields;
 | |
| 	struct ast_variable *it_fields;
 | |
| 	int i;
 | |
| 	SCOPED_MUTEX(lock, &metrics_lock);
 | |
| 
 | |
| 	/*
 | |
| 	 * Because our object is opaque, we have to do some pretty ... interesting
 | |
| 	 * things here to try and figure out what just happened.
 | |
| 	 */
 | |
| 	fields = ast_sorcery_objectset_create(ast_sip_get_sorcery(), obj);
 | |
| 	if (!fields) {
 | |
| 		ast_debug(1, "Unable to convert presumed registry object %p to strings; bailing on delete\n", obj);
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	for (it_fields = fields; it_fields; it_fields = it_fields->next) {
 | |
| 		if (strcasecmp(it_fields->name, "client_uri")) {
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 		for (i = 0; i < AST_VECTOR_SIZE(&metrics); i++) {
 | |
| 			struct prometheus_metric_wrapper *wrapper = AST_VECTOR_GET(&metrics, i);
 | |
| 
 | |
| 			if (strcmp(wrapper->key, it_fields->value)) {
 | |
| 				continue;
 | |
| 			}
 | |
| 
 | |
| 			ast_debug(1, "Registration metric '%s' deleted; purging with prejudice\n", wrapper->key);
 | |
| 			AST_VECTOR_REMOVE(&metrics, i, 1);
 | |
| 			/* This will free the metric as well */
 | |
| 			prometheus_metric_unregister(wrapper->metric);
 | |
| 			ast_free(wrapper);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	ast_variables_destroy(fields);
 | |
| }
 | |
| 
 | |
| static const struct ast_sorcery_observer registration_observer = {
 | |
| 	.deleted = registration_deleted_observer,
 | |
| };
 | |
| 
 | |
| /*!
 | |
|  * \internal
 | |
|  * \brief Sorcery observer called when an object is loaded/reloaded
 | |
|  *
 | |
|  * \param name The name of the object
 | |
|  * \param sorcery The sorcery handle
 | |
|  * \param object_type The type of object
 | |
|  * \param reloaded Whether or not we reloaded the state/definition of the object
 | |
|  *
 | |
|  * \details
 | |
|  * In our case, we only care when we re-load the registration object. We
 | |
|  * wait for the registration to occur in order to create our Prometheus
 | |
|  * metric, so we just punt on object creation. On reload, however, fundamental
 | |
|  * properties of the metric may have been changed, which means we have to remove
 | |
|  * the existing definition of the metric and allow the new registration stasis
 | |
|  * message to re-build it.
 | |
|  */
 | |
| static void registration_loaded_observer(const char *name, const struct ast_sorcery *sorcery, const char *object_type, int reloaded)
 | |
| {
 | |
| 	SCOPED_MUTEX(lock, &metrics_lock);
 | |
| 	int i;
 | |
| 
 | |
| 	if (!reloaded) {
 | |
| 		/* Meh */
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	if (strcmp(object_type, "registration")) {
 | |
| 		/* Not interested */
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	for (i = 0; i < AST_VECTOR_SIZE(&metrics); i++) {
 | |
| 		struct prometheus_metric_wrapper *wrapper = AST_VECTOR_GET(&metrics, i);
 | |
| 		struct ast_variable search_fields = {
 | |
| 			.name = "client_uri",
 | |
| 			.value = wrapper->key,
 | |
| 			.next = NULL,
 | |
| 		};
 | |
| 		void *obj;
 | |
| 
 | |
| 		ast_debug(1, "Checking for the existance of registration metric %s\n", wrapper->key);
 | |
| 		obj = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), object_type, AST_RETRIEVE_FLAG_DEFAULT, &search_fields);
 | |
| 		if (!obj) {
 | |
| 			ast_debug(1, "Registration metric '%s' not found; purging with prejudice\n", wrapper->key);
 | |
| 			AST_VECTOR_REMOVE(&metrics, i, 1);
 | |
| 			/* This will free the metric as well */
 | |
| 			prometheus_metric_unregister(wrapper->metric);
 | |
| 			ast_free(wrapper);
 | |
| 			continue;
 | |
| 		}
 | |
| 		ao2_ref(obj, -1);
 | |
| 	}
 | |
| 
 | |
| }
 | |
| 
 | |
| static const struct ast_sorcery_instance_observer observer_callbacks_registrations = {
 | |
| 	.object_type_loaded = registration_loaded_observer,
 | |
| };
 | |
| 
 | |
| /*!
 | |
|  * \internal
 | |
|  * \brief Callback for Stasis Registry messages
 | |
|  *
 | |
|  * \param data Callback data, always NULL
 | |
|  * \param sub Stasis subscription
 | |
|  * \param message Our Registry message
 | |
|  *
 | |
|  * \details
 | |
|  * The Stasis Registry message both updates the state of the Prometheus metric
 | |
|  * as well as forces its creation.
 | |
|  */
 | |
| static void registry_message_cb(void *data, struct stasis_subscription *sub,
 | |
| 	struct stasis_message *message)
 | |
| {
 | |
| 	struct ast_json_payload *payload = stasis_message_data(message);
 | |
| 	struct ast_json *json = payload->json;
 | |
| 	const char *username = ast_json_string_get(ast_json_object_get(json, "username"));
 | |
| 	const char *status_str = ast_json_string_get(ast_json_object_get(json, "status"));
 | |
| 	const char *domain = ast_json_string_get(ast_json_object_get(json, "domain"));
 | |
| 	const char *channel_type = ast_json_string_get(ast_json_object_get(json, "channeltype"));
 | |
| 	struct prometheus_metric metric = PROMETHEUS_METRIC_STATIC_INITIALIZATION(
 | |
| 		PROMETHEUS_METRIC_GAUGE,
 | |
| 		"asterisk_pjsip_outbound_registration_status",
 | |
| 		"Current registration status. 0=Unregistered; 1=Registered; 2=Rejected.",
 | |
| 		NULL
 | |
| 	);
 | |
| 	struct prometheus_metric_wrapper *wrapper;
 | |
| 	char eid_str[32];
 | |
| 
 | |
| 	ast_eid_to_str(eid_str, sizeof(eid_str), &ast_eid_default);
 | |
| 
 | |
| 	PROMETHEUS_METRIC_SET_LABEL(&metric, 0, "eid", eid_str);
 | |
| 	PROMETHEUS_METRIC_SET_LABEL(&metric, 1, "username", username);
 | |
| 	PROMETHEUS_METRIC_SET_LABEL(&metric, 2, "domain", domain);
 | |
| 	PROMETHEUS_METRIC_SET_LABEL(&metric, 3, "channel_type", channel_type);
 | |
| 	snprintf(metric.value, sizeof(metric.value), "%d", registration_state_to_int(status_str));
 | |
| 
 | |
| 	wrapper = get_wrapper(username);
 | |
| 	if (wrapper) {
 | |
| 		ast_mutex_lock(&wrapper->metric->lock);
 | |
| 		/* Safe */
 | |
| 		strcpy(wrapper->metric->value, metric.value);
 | |
| 		ast_mutex_unlock(&wrapper->metric->lock);
 | |
| 	} else {
 | |
| 		wrapper = create_wrapper(username);
 | |
| 		if (!wrapper) {
 | |
| 			return;
 | |
| 		}
 | |
| 
 | |
| 		wrapper->metric = prometheus_gauge_create(metric.name, metric.help);
 | |
| 		if (!wrapper->metric) {
 | |
| 			ast_free(wrapper);
 | |
| 			return;
 | |
| 		}
 | |
| 		*(wrapper->metric) = metric;
 | |
| 
 | |
| 		prometheus_metric_register(wrapper->metric);
 | |
| 		AST_VECTOR_APPEND(&metrics, wrapper);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| #endif /* HAVE_PJPROJECT */
 | |
| 
 | |
| /*!
 | |
|  * \internal
 | |
|  * \brief Callback invoked when the core module is unloaded
 | |
|  */
 | |
| static void pjsip_outbound_registration_metrics_unload_cb(void)
 | |
| {
 | |
| #ifdef HAVE_PJPROJECT
 | |
| 	stasis_message_router_unsubscribe_and_join(router);
 | |
| 	router = NULL;
 | |
| 	ast_sorcery_instance_observer_remove(ast_sip_get_sorcery(), &observer_callbacks_registrations);
 | |
| 	ast_sorcery_observer_remove(ast_sip_get_sorcery(), "registration", ®istration_observer);
 | |
| #endif /* HAVE_PJPROJECT */
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \internal
 | |
|  * \brief Metrics provider definition
 | |
|  */
 | |
| static struct prometheus_metrics_provider provider = {
 | |
| 	.name = "pjsip_outbound_registration",
 | |
| 	.unload_cb = pjsip_outbound_registration_metrics_unload_cb,
 | |
| };
 | |
| 
 | |
| int pjsip_outbound_registration_metrics_init(void)
 | |
| {
 | |
| 	prometheus_metrics_provider_register(&provider);
 | |
| 
 | |
| #ifdef HAVE_PJPROJECT
 | |
| 	router = stasis_message_router_create(ast_system_topic());
 | |
| 	if (!router) {
 | |
| 		goto cleanup;
 | |
| 	}
 | |
| 
 | |
| 	if (stasis_message_router_add(router, ast_system_registry_type(), registry_message_cb, NULL)) {
 | |
| 		goto cleanup;
 | |
| 	}
 | |
| 
 | |
| 	if (ast_sorcery_instance_observer_add(ast_sip_get_sorcery(), &observer_callbacks_registrations)) {
 | |
| 		goto cleanup;
 | |
| 	}
 | |
| 
 | |
| 	if (ast_sorcery_observer_add(ast_sip_get_sorcery(), "registration", ®istration_observer)) {
 | |
| 		goto cleanup;
 | |
| 	}
 | |
| #endif /* HAVE_PJPROJECT */
 | |
| 	return 0;
 | |
| 
 | |
| #ifdef HAVE_PJPROJECT
 | |
| cleanup:
 | |
| 	ao2_cleanup(router);
 | |
| 	router = NULL;
 | |
| 	ast_sorcery_instance_observer_remove(ast_sip_get_sorcery(), &observer_callbacks_registrations);
 | |
| 	ast_sorcery_observer_remove(ast_sip_get_sorcery(), "registration", ®istration_observer);
 | |
| 
 | |
| 	return -1;
 | |
| #endif /* HAVE_PJPROJECT */
 | |
| }
 |