Asterisk - The Open Source Telephony Project  18.5.0
pjsip_outbound_registrations.c
Go to the documentation of this file.
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2019 Sangoma, Inc.
5  *
6  * Matt Jordan <[email protected]>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18 
19 /*!
20  * \file
21  * \brief Prometheus PJSIP Outbound Registration Metrics
22  *
23  * \author Matt Jordan <[email protected]>
24  *
25  */
26 
27 #include "asterisk.h"
28 
30 #include "asterisk/stasis_system.h"
32 
33 #ifdef HAVE_PJPROJECT
34 #include "asterisk/res_pjsip.h"
35 #endif /* HAVE_PJPROJECT */
36 
37 #include "prometheus_internal.h"
38 
39 #ifdef HAVE_PJPROJECT
40 
41 /*! \internal \brief Our one and only Stasis message router */
43 
44 /*!
45  * \internal
46  * \brief Wrapper object around our Metrics
47  *
48  * \details We keep a wrapper around the metric so we can easily
49  * update the value when the state of the registration changes, as
50  * well as remove and unregsiter the metric when someone destroys
51  * or reloads the registration
52  */
54  /*!
55  * \brief The actual metric. Worth noting that we do *NOT*
56  * own the metric, as it is registered with res_prometheus.
57  * Luckily, that module doesn't destroy metrics unless we
58  * tell it to or if the module unloads.
59  */
61  /*!
62  * \brief Unique key to look up the metric
63  */
64  char key[128];
65 };
66 
68 
69 /*!
70  * \internal Vector of metric wrappers
71  *
72  * \details
73  * Why a vector and not an ao2_container? Two reasons:
74  * (1) There's rarely a ton of outbound registrations, so an ao2_container
75  * is overkill when we can just walk a vector
76  * (2) The lifetime of wrappers is well contained
77  */
78 static AST_VECTOR(, struct prometheus_metric_wrapper *) metrics;
79 
80 /*!
81  * \internal
82  * \brief Create a wrapper for a metric given a key
83  *
84  * \param key The unique key
85  *
86  * \retval NULL on error
87  * \retval malloc'd metric wrapper on success
88  */
89 static struct prometheus_metric_wrapper *create_wrapper(const char *key)
90 {
91  struct prometheus_metric_wrapper *wrapper;
92 
93  wrapper = ast_calloc(1, sizeof(*wrapper));
94  if (!wrapper) {
95  return NULL;
96  }
97 
98  ast_copy_string(wrapper->key, key, sizeof(wrapper->key));
99  return wrapper;
100 }
101 
102 /*!
103  * \internal
104  * \brief Get a wrapper by its key
105  *
106  * \param key The unqiue key for the wrapper
107  *
108  * \retval NULL on no wrapper found :-\
109  * \retval wrapper on success
110  */
111 static struct prometheus_metric_wrapper *get_wrapper(const char *key)
112 {
113  int i;
115 
116  for (i = 0; i < AST_VECTOR_SIZE(&metrics); i++) {
117  struct prometheus_metric_wrapper *wrapper = AST_VECTOR_GET(&metrics, i);
118 
119  if (!strcmp(wrapper->key, key)) {
120  return wrapper;
121  }
122  }
123 
124  return NULL;
125 }
126 
127 /*!
128  * \internal
129  * \brief Convert an outbound registration state to a numeric value
130  *
131  * \param state The state to convert
132  *
133  * \retval int representation of the state
134  */
135 static int registration_state_to_int(const char *state)
136 {
137  if (!strcasecmp(state, "Registered")) {
138  return 1;
139  } else if (!strcasecmp(state, "Rejected")) {
140  return 2;
141  }
142  return 0;
143 }
144 
145 /*!
146  * \internal
147  * \brief Sorcery observer callback called when a registration object is deleted
148  *
149  * \param obj The opaque object that was deleted
150  */
151 static void registration_deleted_observer(const void *obj)
152 {
153  struct ast_variable *fields;
154  struct ast_variable *it_fields;
155  int i;
157 
158  /*
159  * Because our object is opaque, we have to do some pretty ... interesting
160  * things here to try and figure out what just happened.
161  */
163  if (!fields) {
164  ast_debug(1, "Unable to convert presumed registry object %p to strings; bailing on delete\n", obj);
165  return;
166  }
167 
168  for (it_fields = fields; it_fields; it_fields = it_fields->next) {
169  if (strcasecmp(it_fields->name, "client_uri")) {
170  continue;
171  }
172 
173  for (i = 0; i < AST_VECTOR_SIZE(&metrics); i++) {
174  struct prometheus_metric_wrapper *wrapper = AST_VECTOR_GET(&metrics, i);
175 
176  if (strcmp(wrapper->key, it_fields->value)) {
177  continue;
178  }
179 
180  ast_debug(1, "Registration metric '%s' deleted; purging with prejudice\n", wrapper->key);
181  AST_VECTOR_REMOVE(&metrics, i, 1);
182  /* This will free the metric as well */
184  ast_free(wrapper);
185  }
186  }
187 
188  ast_variables_destroy(fields);
189 }
190 
193 };
194 
195 /*!
196  * \internal
197  * \brief Sorcery observer called when an object is loaded/reloaded
198  *
199  * \param name The name of the object
200  * \param sorcery The sorcery handle
201  * \param object_type The type of object
202  * \param reloaded Whether or not we reloaded the state/definition of the object
203  *
204  * \details
205  * In our case, we only care when we re-load the registration object. We
206  * wait for the registration to occur in order to create our Prometheus
207  * metric, so we just punt on object creation. On reload, however, fundamental
208  * properties of the metric may have been changed, which means we have to remove
209  * the existing definition of the metric and allow the new registration stasis
210  * message to re-build it.
211  */
212 static void registration_loaded_observer(const char *name, const struct ast_sorcery *sorcery, const char *object_type, int reloaded)
213 {
215  int i;
216 
217  if (!reloaded) {
218  /* Meh */
219  return;
220  }
221 
222  if (strcmp(object_type, "registration")) {
223  /* Not interested */
224  return;
225  }
226 
227  for (i = 0; i < AST_VECTOR_SIZE(&metrics); i++) {
228  struct prometheus_metric_wrapper *wrapper = AST_VECTOR_GET(&metrics, i);
229  struct ast_variable search_fields = {
230  .name = "client_uri",
231  .value = wrapper->key,
232  .next = NULL,
233  };
234  void *obj;
235 
236  ast_debug(1, "Checking for the existance of registration metric %s\n", wrapper->key);
238  if (!obj) {
239  ast_debug(1, "Registration metric '%s' not found; purging with prejudice\n", wrapper->key);
240  AST_VECTOR_REMOVE(&metrics, i, 1);
241  /* This will free the metric as well */
243  ast_free(wrapper);
244  continue;
245  }
246  ao2_ref(obj, -1);
247  }
248 
249 }
250 
253 };
254 
255 /*!
256  * \internal
257  * \brief Callback for Stasis Registry messages
258  *
259  * \param data Callback data, always NULL
260  * \param sub Stasis subscription
261  * \param message Our Registry message
262  *
263  * \details
264  * The Stasis Registry message both updates the state of the Prometheus metric
265  * as well as forces its creation.
266  */
267 static void registry_message_cb(void *data, struct stasis_subscription *sub,
268  struct stasis_message *message)
269 {
270  struct ast_json_payload *payload = stasis_message_data(message);
271  struct ast_json *json = payload->json;
272  const char *username = ast_json_string_get(ast_json_object_get(json, "username"));
273  const char *status_str = ast_json_string_get(ast_json_object_get(json, "status"));
274  const char *domain = ast_json_string_get(ast_json_object_get(json, "domain"));
275  const char *channel_type = ast_json_string_get(ast_json_object_get(json, "channeltype"));
278  "asterisk_pjsip_outbound_registration_status",
279  "Current registration status. 0=Unregistered; 1=Registered; 2=Rejected.",
280  NULL
281  );
282  struct prometheus_metric_wrapper *wrapper;
283  char eid_str[32];
284 
285  ast_eid_to_str(eid_str, sizeof(eid_str), &ast_eid_default);
286 
287  PROMETHEUS_METRIC_SET_LABEL(&metric, 0, "eid", eid_str);
288  PROMETHEUS_METRIC_SET_LABEL(&metric, 1, "username", username);
289  PROMETHEUS_METRIC_SET_LABEL(&metric, 2, "domain", domain);
290  PROMETHEUS_METRIC_SET_LABEL(&metric, 3, "channel_type", channel_type);
291  snprintf(metric.value, sizeof(metric.value), "%d", registration_state_to_int(status_str));
292 
293  wrapper = get_wrapper(username);
294  if (wrapper) {
295  ast_mutex_lock(&wrapper->metric->lock);
296  /* Safe */
297  strcpy(wrapper->metric->value, metric.value);
298  ast_mutex_unlock(&wrapper->metric->lock);
299  } else {
300  wrapper = create_wrapper(username);
301  if (!wrapper) {
302  return;
303  }
304 
305  wrapper->metric = prometheus_gauge_create(metric.name, metric.help);
306  if (!wrapper->metric) {
307  ast_free(wrapper);
308  return;
309  }
310  *(wrapper->metric) = metric;
311 
313  AST_VECTOR_APPEND(&metrics, wrapper);
314  }
315 }
316 
317 #endif /* HAVE_PJPROJECT */
318 
319 /*!
320  * \internal
321  * \brief Callback invoked when the core module is unloaded
322  */
324 {
325 #ifdef HAVE_PJPROJECT
327  router = NULL;
328  ast_sorcery_instance_observer_remove(ast_sip_get_sorcery(), &observer_callbacks_registrations);
329  ast_sorcery_observer_remove(ast_sip_get_sorcery(), "registration", &registration_observer);
330 #endif /* HAVE_PJPROJECT */
331 }
332 
333 /*!
334  * \internal
335  * \brief Metrics provider definition
336  */
338  .name = "pjsip_outbound_registration",
340 };
341 
343 {
345 
346 #ifdef HAVE_PJPROJECT
348  if (!router) {
349  goto cleanup;
350  }
351 
353  goto cleanup;
354  }
355 
356  if (ast_sorcery_instance_observer_add(ast_sip_get_sorcery(), &observer_callbacks_registrations)) {
357  goto cleanup;
358  }
359 
360  if (ast_sorcery_observer_add(ast_sip_get_sorcery(), "registration", &registration_observer)) {
361  goto cleanup;
362  }
363 #endif /* HAVE_PJPROJECT */
364  return 0;
365 
366 #ifdef HAVE_PJPROJECT
367 cleanup:
368  ao2_cleanup(router);
369  router = NULL;
370  ast_sorcery_instance_observer_remove(ast_sip_get_sorcery(), &observer_callbacks_registrations);
371  ast_sorcery_observer_remove(ast_sip_get_sorcery(), "registration", &registration_observer);
372 
373  return -1;
374 #endif /* HAVE_PJPROJECT */
375 }
struct ast_variable * next
Asterisk main include file. File version handling, generic pbx functions.
An actual, honest to god, metric.
void(* deleted)(const void *object)
Callback for when an object is deleted.
Definition: sorcery.h:340
int prometheus_metric_register(struct prometheus_metric *metric)
void ast_variables_destroy(struct ast_variable *var)
Free variable list.
Definition: extconf.c:1263
int pjsip_outbound_registration_metrics_init(void)
Initialize PJSIP outbound registration metrics.
char * ast_eid_to_str(char *s, int maxlen, struct ast_eid *eid)
Convert an EID to a string.
Definition: main/utils.c:2587
int ast_sorcery_instance_observer_add(struct ast_sorcery *sorcery, const struct ast_sorcery_instance_observer *callbacks)
Add an observer to a sorcery instance.
Definition: sorcery.c:520
static struct stasis_message_router * router
int stasis_message_router_add(struct stasis_message_router *router, struct stasis_message_type *message_type, stasis_subscription_cb callback, void *data)
Add a route to a message router.
struct prometheus_metric * metric
The actual metric. Worth noting that we do NOT own the metric, as it is registered with res_prometheu...
void stasis_message_router_unsubscribe_and_join(struct stasis_message_router *router)
Unsubscribe the router from the upstream topic, blocking until the final message has been processed...
static void registry_message_cb(void *data, struct stasis_subscription *sub, struct stasis_message *message)
Structure for variables, used for configurations and for channel variables.
#define AST_VECTOR_APPEND(vec, elem)
Append an element to a vector, growing the vector if needed.
Definition: vector.h:256
struct ast_json * json
Definition: json.h:1025
Full structure for sorcery.
Definition: sorcery.c:230
#define ast_mutex_lock(a)
Definition: lock.h:187
int prometheus_metric_unregister(struct prometheus_metric *metric)
Remove a registered metric.
static AST_VECTOR(struct prometheus_metric_wrapper *)
#define NULL
Definition: resample.c:96
Domain data structure.
Definition: sip.h:888
#define PROMETHEUS_METRIC_STATIC_INITIALIZATION(mtype, n, h, cb)
Convenience macro for initializing a metric on the stack.
static void registration_loaded_observer(const char *name, const struct ast_sorcery *sorcery, const char *object_type, int reloaded)
static const struct ast_sorcery_instance_observer observer_callbacks_registrations
#define ast_debug(level,...)
Log a DEBUG message.
Definition: logger.h:452
#define PROMETHEUS_METRIC_SET_LABEL(metric, label, n, v)
Convenience macro for setting a label / value in a metric.
#define SCOPED_MUTEX(varname, lock)
scoped lock specialization for mutexes
Definition: lock.h:587
static const struct ast_sorcery_observer registration_observer
Interface for the sorcery instance observer.
Definition: sorcery.h:237
ast_mutex_t lock
Definition: app_meetme.c:1091
#define ao2_ref(o, delta)
Definition: astobj2.h:464
const char * ast_json_string_get(const struct ast_json *string)
Get the value of a JSON string.
Definition: json.c:273
void prometheus_metrics_provider_register(const struct prometheus_metrics_provider *provider)
Register a metrics provider.
struct prometheus_metric * prometheus_gauge_create(const char *name, const char *help)
Create a malloc&#39;d gauge metric.
#define stasis_message_router_create(topic)
void ast_sorcery_instance_observer_remove(struct ast_sorcery *sorcery, const struct ast_sorcery_instance_observer *callbacks)
Remove an observer from a sorcery instance.
Definition: sorcery.c:537
int ast_sorcery_observer_add(const struct ast_sorcery *sorcery, const char *type, const struct ast_sorcery_observer *callbacks)
Add an observer to a specific object type.
Definition: sorcery.c:2386
Default retrieval flags.
Definition: sorcery.h:117
void * stasis_message_data(const struct stasis_message *msg)
Get the data contained in a message.
Interface for a sorcery object type observer.
Definition: sorcery.h:332
struct stasis_message_type * ast_system_registry_type(void)
A stasis_message_type for outbound registration.
char value[PROMETHEUS_MAX_VALUE_LENGTH]
The current value.
struct stasis_topic * ast_system_topic(void)
A Stasis Message Bus API topic which publishes messages regarding system changes. ...
#define ast_sorcery_objectset_create(sorcery, object)
Create an object set (KVP list) for an object.
Definition: sorcery.h:1136
const char * help
Pointer to a static string defining this metric&#39;s help text.
static const char name[]
Definition: cdr_mysql.c:74
#define ast_free(a)
Definition: astmm.h:182
#define ast_calloc(num, len)
A wrapper for calloc()
Definition: astmm.h:204
static int registration_state_to_int(const char *state)
char key[128]
Unique key to look up the metric.
static void registration_deleted_observer(const void *obj)
static void * cleanup(void *unused)
Definition: pbx_realtime.c:124
const char * name
Handy name of the provider for debugging purposes.
static struct prometheus_metric_wrapper * get_wrapper(const char *key)
void * ast_sorcery_retrieve_by_fields(const struct ast_sorcery *sorcery, const char *type, unsigned int flags, struct ast_variable *fields)
Retrieve an object or multiple objects using specific fields.
Definition: sorcery.c:1897
char name[PROMETHEUS_MAX_NAME_LENGTH]
Our metric name.
static ast_mutex_t metrics_lock
struct ast_eid ast_eid_default
Global EID.
Definition: options.c:93
void(* object_type_loaded)(const char *name, const struct ast_sorcery *sorcery, const char *object_type, int reloaded)
Callback after any object_type is loaded/reloaded.
Definition: sorcery.h:260
#define AST_VECTOR_GET(vec, idx)
Get an element from a vector.
Definition: vector.h:682
void ast_sorcery_observer_remove(const struct ast_sorcery *sorcery, const char *type, const struct ast_sorcery_observer *callbacks)
Remove an observer from a specific object type.
Definition: sorcery.c:2418
static struct ast_sorcery * sorcery
struct ast_sorcery * ast_sip_get_sorcery(void)
Get a pointer to the SIP sorcery structure.
#define ao2_cleanup(obj)
Definition: astobj2.h:1958
struct ast_json * ast_json_object_get(struct ast_json *object, const char *key)
Get a field from a JSON object.
Definition: json.c:397
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
Definition: strings.h:401
A function table for a metrics provider.
ast_mutex_t lock
A lock protecting the metric value.
static const char channel_type[]
Definition: chan_motif.c:321
struct stasis_forward * sub
Definition: res_corosync.c:240
Abstract JSON element (object, array, string, int, ...).
static struct prometheus_metrics_provider provider
#define AST_VECTOR_REMOVE(vec, idx, preserve_ordered)
Remove an element from a vector by index.
Definition: vector.h:412
static void pjsip_outbound_registration_metrics_unload_cb(void)
#define AST_VECTOR_SIZE(vec)
Get the number of elements in a vector.
Definition: vector.h:611
#define AST_MUTEX_DEFINE_STATIC(mutex)
Definition: lock.h:518
#define ast_mutex_unlock(a)
Definition: lock.h:188