Asterisk - The Open Source Telephony Project  21.4.1
res_prometheus.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 <mjordan@digium.com>
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 Core Prometheus metrics API
22  *
23  * \author Matt Jordan <mjordan@digium.com>
24  *
25  */
26 
27 /*** MODULEINFO
28  <use>pjproject</use>
29  <use type="module">res_pjsip</use>
30  <use type="module">res_pjsip_outbound_registration</use>
31  <support_level>extended</support_level>
32  ***/
33 
34 /*** DOCUMENTATION
35  <configInfo name="res_prometheus" language="en_US">
36  <synopsis>Resource for integration with Prometheus</synopsis>
37  <configFile name="prometheus.conf">
38  <configObject name="general">
39  <synopsis>General settings.</synopsis>
40  <description>
41  <para>
42  The <emphasis>general</emphasis> settings section contains information
43  to configure Asterisk to serve up statistics for a Prometheus server.
44  </para>
45  <note>
46  <para>You must enable Asterisk's HTTP server in <filename>http.conf</filename>
47  for this module to function properly!
48  </para>
49  </note>
50  </description>
51  <configOption name="enabled" default="no">
52  <synopsis>Enable or disable Prometheus statistics.</synopsis>
53  <description>
54  <enumlist>
55  <enum name="no" />
56  <enum name="yes" />
57  </enumlist>
58  </description>
59  </configOption>
60  <configOption name="core_metrics_enabled" default="yes">
61  <synopsis>Enable or disable core metrics.</synopsis>
62  <description>
63  <para>
64  Core metrics show various properties of the Asterisk system, including
65  how the binary was built, the version, uptime, last reload time, etc.
66  Generally, these options are harmless and should always be enabled.
67  This option mostly exists to disable output of all options for testing
68  purposes, as well as for those foolish souls who really don't care
69  what version of Asterisk they're running.
70  </para>
71  <enumlist>
72  <enum name="no" />
73  <enum name="yes" />
74  </enumlist>
75  </description>
76  </configOption>
77  <configOption name="uri" default="metrics">
78  <synopsis>The HTTP URI to serve metrics up on.</synopsis>
79  </configOption>
80  <configOption name="auth_username">
81  <synopsis>Username to use for Basic Auth.</synopsis>
82  <description>
83  <para>
84  If set, use Basic Auth to authenticate requests to the route
85  specified by <replaceable>uri</replaceable>. Note that you
86  will need to configure your Prometheus server with the
87  appropriate auth credentials.
88  </para>
89  <para>
90  If set, <replaceable>auth_password</replaceable> must also
91  be set appropriately.
92  </para>
93  <warning>
94  <para>
95  It is highly recommended to set up Basic Auth. Failure
96  to do so may result in useful information about your
97  Asterisk system being made easily scrapable by the
98  wide world. Consider yourself duly warned.
99  </para>
100  </warning>
101  </description>
102  </configOption>
103  <configOption name="auth_password">
104  <synopsis>Password to use for Basic Auth.</synopsis>
105  <description>
106  <para>
107  If set, this is used in conjunction with <replaceable>auth_username</replaceable>
108  to require Basic Auth for all requests to the Prometheus metrics. Note that
109  setting this without <replaceable>auth_username</replaceable> will not
110  do anything.
111  </para>
112  </description>
113  </configOption>
114  <configOption name="auth_realm" default="Asterisk Prometheus Metrics">
115  <synopsis>Auth realm used in challenge responses</synopsis>
116  </configOption>
117  </configObject>
118  </configFile>
119  </configInfo>
120 ***/
121 
122 #define AST_MODULE_SELF_SYM __internal_res_prometheus_self
123 
124 #include "asterisk.h"
125 
126 #include "asterisk/module.h"
127 #include "asterisk/vector.h"
128 #include "asterisk/http.h"
129 #include "asterisk/config_options.h"
130 #include "asterisk/ast_version.h"
131 #include "asterisk/buildinfo.h"
132 #include "asterisk/res_prometheus.h"
133 
135 
136 /*! \brief Lock that protects data structures during an HTTP scrape */
137 AST_MUTEX_DEFINE_STATIC(scrape_lock);
138 
139 AST_VECTOR(, struct prometheus_metric *) metrics;
140 
141 AST_VECTOR(, struct prometheus_callback *) callbacks;
142 
143 AST_VECTOR(, const struct prometheus_metrics_provider *) providers;
144 
145 static struct timeval last_scrape;
146 
147 /*! \brief The actual module config */
148 struct module_config {
149  /*! \brief General settings */
151 };
152 
153 static struct aco_type global_option = {
154  .type = ACO_GLOBAL,
155  .name = "general",
156  .item_offset = offsetof(struct module_config, general),
157  .category_match = ACO_WHITELIST_EXACT,
158  .category = "general",
159 };
160 
161 struct aco_type *global_options[] = ACO_TYPES(&global_option);
162 
163 struct aco_file prometheus_conf = {
164  .filename = "prometheus.conf",
165  .types = ACO_TYPES(&global_option),
166 };
167 
168 /*! \brief The module configuration container */
170 
171 static void *module_config_alloc(void);
172 static int prometheus_config_pre_apply(void);
173 static void prometheus_config_post_apply(void);
174 /*! \brief Register information about the configs being processed by this module */
176  .files = ACO_FILES(&prometheus_conf),
177  .pre_apply_config = prometheus_config_pre_apply,
178  .post_apply_config = prometheus_config_post_apply,
179 );
180 
181 #define CORE_PROPERTIES_HELP "Asterisk instance properties. The value of this will always be 1."
182 
183 #define CORE_UPTIME_HELP "Asterisk instance uptime in seconds."
184 
185 #define CORE_LAST_RELOAD_HELP "Time since last Asterisk reload in seconds."
186 
187 #define CORE_METRICS_SCRAPE_TIME_HELP "Total time taken to collect metrics, in milliseconds"
188 
189 static void get_core_uptime_cb(struct prometheus_metric *metric)
190 {
191  struct timeval now = ast_tvnow();
192  int64_t duration = ast_tvdiff_sec(now, ast_startuptime);
193 
194  snprintf(metric->value, sizeof(metric->value), "%" PRIu64, duration);
195 }
196 
197 static void get_last_reload_cb(struct prometheus_metric *metric)
198 {
199  struct timeval now = ast_tvnow();
200  int64_t duration = ast_tvdiff_sec(now, ast_lastreloadtime);
201 
202  snprintf(metric->value, sizeof(metric->value), "%" PRIu64, duration);
203 }
204 
205 /*!
206  * \brief The scrape duration metric
207  *
208  * \details
209  * This metric is special in that it should never be registered.
210  * Instead, the HTTP callback function that walks the metrics will
211  * always populate this metric explicitly if core metrics
212  * are enabled.
213  */
217  "asterisk_core_scrape_time_ms",
218  CORE_METRICS_SCRAPE_TIME_HELP,
219  NULL);
220 
221 #define METRIC_CORE_PROPS_ARRAY_INDEX 0
222 /*!
223  * \brief Core metrics to scrape
224  */
225 static struct prometheus_metric core_metrics[] = {
228  "asterisk_core_properties",
229  CORE_PROPERTIES_HELP,
230  NULL),
233  "asterisk_core_uptime_seconds",
234  CORE_UPTIME_HELP,
235  get_core_uptime_cb),
238  "asterisk_core_last_reload_seconds",
239  CORE_LAST_RELOAD_HELP,
240  get_last_reload_cb),
241 };
242 
243 /*!
244  * \internal
245  * \brief Compare two metrics to see if their name / labels / values match
246  *
247  * \param left The first metric to compare
248  * \param right The second metric to compare
249  *
250  * \retval 0 The metrics are not the same
251  * \retval 1 The metrics are the same
252  */
253 static int prometheus_metric_cmp(struct prometheus_metric *left,
254  struct prometheus_metric *right)
255 {
256  int i;
257  ast_debug(5, "Comparison: Names %s == %s\n", left->name, right->name);
258  if (strcmp(left->name, right->name)) {
259  return 0;
260  }
261 
262  for (i = 0; i < PROMETHEUS_MAX_LABELS; i++) {
263  ast_debug(5, "Comparison: Label %d Names %s == %s\n", i,
264  left->labels[i].name, right->labels[i].name);
265  if (strcmp(left->labels[i].name, right->labels[i].name)) {
266  return 0;
267  }
268 
269  ast_debug(5, "Comparison: Label %d Values %s == %s\n", i,
270  left->labels[i].value, right->labels[i].value);
271  if (strcmp(left->labels[i].value, right->labels[i].value)) {
272  return 0;
273  }
274  }
275 
276  ast_debug(5, "Copmarison: %s (%p) is equal to %s (%p)\n",
277  left->name, left, right->name, right);
278  return 1;
279 }
280 
282 {
284 
285  return AST_VECTOR_SIZE(&metrics);
286 }
287 
289 {
291  int i;
292 
293  if (!metric) {
294  return -1;
295  }
296 
297  for (i = 0; i < AST_VECTOR_SIZE(&metrics); i++) {
298  struct prometheus_metric *existing = AST_VECTOR_GET(&metrics, i);
299  struct prometheus_metric *child;
300 
301  if (prometheus_metric_cmp(existing, metric)) {
302  ast_log(AST_LOG_NOTICE,
303  "Refusing registration of existing Prometheus metric: %s\n",
304  metric->name);
305  return -1;
306  }
307 
308  AST_LIST_TRAVERSE(&existing->children, child, entry) {
309  if (prometheus_metric_cmp(child, metric)) {
310  ast_log(AST_LOG_NOTICE,
311  "Refusing registration of existing Prometheus metric: %s\n",
312  metric->name);
313  return -1;
314  }
315  }
316 
317  if (!strcmp(metric->name, existing->name)) {
318  ast_debug(3, "Nesting metric '%s' as child (%p) under existing (%p)\n",
319  metric->name, metric, existing);
320  AST_LIST_INSERT_TAIL(&existing->children, metric, entry);
321  return 0;
322  }
323  }
324 
325  ast_debug(3, "Tracking new root metric '%s'\n", metric->name);
326  if (AST_VECTOR_APPEND(&metrics, metric)) {
327  ast_log(AST_LOG_WARNING, "Failed to grow vector to make room for Prometheus metric: %s\n",
328  metric->name);
329  return -1;
330  }
331 
332  return 0;
333 }
334 
336 {
337  if (!metric) {
338  return -1;
339  }
340 
341  {
343  int i;
344 
345  ast_debug(3, "Removing metric '%s'\n", metric->name);
346  for (i = 0; i < AST_VECTOR_SIZE(&metrics); i++) {
347  struct prometheus_metric *existing = AST_VECTOR_GET(&metrics, i);
348 
349  /*
350  * If this is a complete match, remove the matching metric
351  * and place its children back into the list
352  */
353  if (prometheus_metric_cmp(existing, metric)) {
354  struct prometheus_metric *root;
355 
356  AST_VECTOR_REMOVE(&metrics, i, 1);
357  root = AST_LIST_REMOVE_HEAD(&existing->children, entry);
358  if (root) {
359  struct prometheus_metric *child;
360  AST_LIST_TRAVERSE_SAFE_BEGIN(&existing->children, child, entry) {
362  AST_LIST_INSERT_TAIL(&root->children, child, entry);
363  }
365  AST_VECTOR_INSERT_AT(&metrics, i, root);
366  }
367  prometheus_metric_free(existing);
368  return 0;
369  }
370 
371  /*
372  * Name match, but labels don't match. Find the matching entry with
373  * labels and remove it along with all of its children
374  */
375  if (!strcmp(existing->name, metric->name)) {
376  struct prometheus_metric *child;
377 
378  AST_LIST_TRAVERSE_SAFE_BEGIN(&existing->children, child, entry) {
379  if (prometheus_metric_cmp(child, metric)) {
381  prometheus_metric_free(child);
382  return 0;
383  }
384  }
386  }
387  }
388  }
389 
390  return -1;
391 }
392 
394 {
395  struct prometheus_metric *child;
396 
397  if (!metric) {
398  return;
399  }
400 
401  while ((child = AST_LIST_REMOVE_HEAD(&metric->children, entry))) {
402  prometheus_metric_free(child);
403  }
404  ast_mutex_destroy(&metric->lock);
405 
407  return;
408  } else if (metric->allocation_strategy == PROMETHEUS_METRIC_MALLOCD) {
409  ast_free(metric);
410  }
411 }
412 
413 /*!
414  * \internal
415  * \brief Common code for creating a metric
416  *
417  * \param name The name of the metric
418  * \param help Help string to output when rendered. This must be static.
419  *
420  * \retval NULL on failure
421  */
422 static struct prometheus_metric *prometheus_metric_create(const char *name, const char *help)
423 {
424  struct prometheus_metric *metric = NULL;
425 
426  metric = ast_calloc(1, sizeof(*metric));
427  if (!metric) {
428  return NULL;
429  }
431  ast_mutex_init(&metric->lock);
432 
433  ast_copy_string(metric->name, name, sizeof(metric->name));
434  metric->help = help;
435 
436  return metric;
437 }
438 
439 struct prometheus_metric *prometheus_gauge_create(const char *name, const char *help)
440 {
441  struct prometheus_metric *metric;
442 
443  metric = prometheus_metric_create(name, help);
444  if (!metric) {
445  return NULL;
446  }
447  metric->type = PROMETHEUS_METRIC_GAUGE;
448 
449  return metric;
450 }
451 
452 struct prometheus_metric *prometheus_counter_create(const char *name, const char *help)
453 {
454  struct prometheus_metric *metric;
455 
456  metric = prometheus_metric_create(name, help);
457  if (!metric) {
458  return NULL;
459  }
461 
462  return metric;
463 }
464 
465 static const char *prometheus_metric_type_to_string(enum prometheus_metric_type type)
466 {
467  switch (type) {
469  return "counter";
471  return "gauge";
472  default:
473  ast_assert(0);
474  return "unknown";
475  }
476 }
477 
478 /*!
479  * \internal
480  * \brief Render a metric to text
481  *
482  * \param metric The metric to render
483  * \param output The string buffer to append the text to
484  */
485 static void prometheus_metric_full_to_string(struct prometheus_metric *metric,
486  struct ast_str **output)
487 {
488  int i;
489  int labels_exist = 0;
490 
491  ast_str_append(output, 0, "%s", metric->name);
492 
493  for (i = 0; i < PROMETHEUS_MAX_LABELS; i++) {
494  if (!ast_strlen_zero(metric->labels[i].name)) {
495  labels_exist = 1;
496  if (i == 0) {
497  ast_str_append(output, 0, "%s", "{");
498  } else {
499  ast_str_append(output, 0, "%s", ",");
500  }
501  ast_str_append(output, 0, "%s=\"%s\"",
502  metric->labels[i].name,
503  metric->labels[i].value);
504  }
505  }
506 
507  if (labels_exist) {
508  ast_str_append(output, 0, "%s", "}");
509  }
510 
511  /*
512  * If no value exists, put in a 0. That ensures we don't anger Prometheus.
513  */
514  if (ast_strlen_zero(metric->value)) {
515  ast_str_append(output, 0, " 0\n");
516  } else {
517  ast_str_append(output, 0, " %s\n", metric->value);
518  }
519 }
520 
522  struct ast_str **output)
523 {
524  struct prometheus_metric *child;
525 
526  ast_str_append(output, 0, "# HELP %s %s\n", metric->name, metric->help);
527  ast_str_append(output, 0, "# TYPE %s %s\n", metric->name,
528  prometheus_metric_type_to_string(metric->type));
529  prometheus_metric_full_to_string(metric, output);
530  AST_LIST_TRAVERSE(&metric->children, child, entry) {
531  prometheus_metric_full_to_string(child, output);
532  }
533 }
534 
536 {
538 
539  if (!callback || !callback->callback_fn || ast_strlen_zero(callback->name)) {
540  return -1;
541  }
542 
543  AST_VECTOR_APPEND(&callbacks, callback);
544 
545  return 0;
546 }
547 
549 {
551  int i;
552 
553  for (i = 0; i < AST_VECTOR_SIZE(&callbacks); i++) {
554  struct prometheus_callback *entry = AST_VECTOR_GET(&callbacks, i);
555 
556  if (!strcmp(callback->name, entry->name)) {
557  AST_VECTOR_REMOVE(&callbacks, i, 1);
558  return;
559  }
560  }
561 }
562 
563 static void scrape_metrics(struct ast_str **response)
564 {
565  int i;
566 
567  for (i = 0; i < AST_VECTOR_SIZE(&callbacks); i++) {
568  struct prometheus_callback *callback = AST_VECTOR_GET(&callbacks, i);
569 
570  if (!callback) {
571  continue;
572  }
573 
574  callback->callback_fn(response);
575  }
576 
577  for (i = 0; i < AST_VECTOR_SIZE(&metrics); i++) {
578  struct prometheus_metric *metric = AST_VECTOR_GET(&metrics, i);
579 
580  if (!metric) {
581  continue;
582  }
583 
584  ast_mutex_lock(&metric->lock);
585  if (metric->get_metric_value) {
586  metric->get_metric_value(metric);
587  }
588  prometheus_metric_to_string(metric, response);
589  ast_mutex_unlock(&metric->lock);
590  }
591 }
592 
593 static int http_callback(struct ast_tcptls_session_instance *ser,
594  const struct ast_http_uri *urih, const char *uri, enum ast_http_method method,
595  struct ast_variable *get_params, struct ast_variable *headers)
596 {
597  RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(global_config), ao2_cleanup);
598  struct ast_str *response = NULL;
599  struct timeval start;
600  struct timeval end;
601 
602  /* If there is no module config or we're not enabled, we can't handle requests */
603  if (!mod_cfg || !mod_cfg->general->enabled) {
604  goto err503;
605  }
606 
607  if (!ast_strlen_zero(mod_cfg->general->auth_username)) {
608  struct ast_http_auth *http_auth;
609 
610  http_auth = ast_http_get_auth(headers);
611  if (!http_auth) {
612  goto err401;
613  }
614 
615  if (strcmp(http_auth->userid, mod_cfg->general->auth_username)) {
616  ast_debug(5, "Invalid username provided for auth request: %s\n", http_auth->userid);
617  ao2_ref(http_auth, -1);
618  goto err401;
619  }
620 
621  if (strcmp(http_auth->password, mod_cfg->general->auth_password)) {
622  ast_debug(5, "Invalid password provided for auth request: %s\n", http_auth->password);
623  ao2_ref(http_auth, -1);
624  goto err401;
625  }
626 
627  ao2_ref(http_auth, -1);
628  }
629 
630  response = ast_str_create(512);
631  if (!response) {
632  goto err500;
633  }
634 
635  start = ast_tvnow();
636 
637  ast_mutex_lock(&scrape_lock);
638 
639  last_scrape = start;
640  scrape_metrics(&response);
641 
642  if (mod_cfg->general->core_metrics_enabled) {
643  int64_t duration;
644 
645  end = ast_tvnow();
646  duration = ast_tvdiff_ms(end, start);
647  snprintf(core_scrape_metric.value,
648  sizeof(core_scrape_metric.value),
649  "%" PRIu64,
650  duration);
651  prometheus_metric_to_string(&core_scrape_metric, &response);
652  }
653  ast_mutex_unlock(&scrape_lock);
654 
655  ast_http_send(ser, method, 200, "OK", NULL, response, 0, 0);
656 
657  return 0;
658 
659 err401:
660  {
661  struct ast_str *auth_challenge_headers;
662 
663  auth_challenge_headers = ast_str_create(128);
664  if (!auth_challenge_headers) {
665  goto err500;
666  }
667  ast_str_append(&auth_challenge_headers, 0,
668  "WWW-Authenticate: Basic realm=\"%s\"\r\n",
669  mod_cfg->general->auth_realm);
670  /* ast_http_send takes ownership of the ast_str */
671  ast_http_send(ser, method, 401, "Unauthorized", auth_challenge_headers, NULL, 0, 1);
672  }
673  ast_free(response);
674  return 0;
675 err503:
676  ast_http_send(ser, method, 503, "Service Unavailable", NULL, NULL, 0, 1);
677  ast_free(response);
678  return 0;
679 err500:
680  ast_http_send(ser, method, 500, "Server Error", NULL, NULL, 0, 1);
681  ast_free(response);
682  return 0;
683 }
684 
686 {
687  struct ast_str *response;
688 
689  response = ast_str_create(512);
690  if (!response) {
691  return NULL;
692  }
693 
694  ast_mutex_lock(&scrape_lock);
695  scrape_metrics(&response);
696  ast_mutex_unlock(&scrape_lock);
697 
698  return response;
699 }
700 
702 {
703  int64_t duration;
704 
705  if (sscanf(core_scrape_metric.value, "%" PRIu64, &duration) != 1) {
706  return -1;
707  }
708 
709  return duration;
710 }
711 
713 {
715 
716  return last_scrape;
717 }
718 
719 static void prometheus_general_config_dtor(void *obj)
720 {
721  struct prometheus_general_config *config = obj;
722 
724 }
725 
727 {
728  struct prometheus_general_config *config;
729 
730  config = ao2_alloc(sizeof(*config), prometheus_general_config_dtor);
731  if (!config || ast_string_field_init(config, 32)) {
732  return NULL;
733  }
734 
735  return config;
736 }
737 
739 {
740  RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(global_config), ao2_cleanup);
741 
742  if (!mod_cfg) {
743  return NULL;
744  }
745  ao2_bump(mod_cfg->general);
746 
747  return mod_cfg->general;
748 }
749 
751 {
752  RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(global_config), ao2_cleanup);
753 
754  if (!mod_cfg) {
755  return;
756  }
757  ao2_replace(mod_cfg->general, config);
759 }
760 
761 
762 /*! \brief Configuration object destructor */
763 static void module_config_dtor(void *obj)
764 {
765  struct module_config *config = obj;
766 
767  if (config->general) {
768  ao2_ref(config->general, -1);
769  }
770 }
771 
772 /*! \brief Module config constructor */
773 static void *module_config_alloc(void)
774 {
775  struct module_config *config;
776 
777  config = ao2_alloc(sizeof(*config), module_config_dtor);
778  if (!config) {
779  return NULL;
780  }
781 
783  if (!config->general) {
784  ao2_ref(config, -1);
785  config = NULL;
786  }
787 
788  return config;
789 }
790 
791 static struct ast_http_uri prometheus_uri = {
792  .description = "Prometheus Metrics URI",
793  .callback = http_callback,
794  .has_subtree = 1,
795  .data = NULL,
796  .key = __FILE__,
797 };
798 
799 /*!
800  * \brief Pre-apply callback for the config framework.
801  *
802  * This validates that required fields exist and are populated.
803  */
805 {
806  struct module_config *config = aco_pending_config(&cfg_info);
807 
808  if (!config->general->enabled) {
809  /* If we're not enabled, we don't care about anything else */
810  return 0;
811  }
812 
813  if (!ast_strlen_zero(config->general->auth_username)
814  && ast_strlen_zero(config->general->auth_password)) {
815  ast_log(AST_LOG_ERROR, "'auth_username' set without a corresponding 'auth_password'\n");
816  return -1;
817  }
818 
819  return 0;
820 }
821 
822 /*!
823  * \brief Post-apply callback for the config framework.
824  *
825  * This sets any run-time information derived from the configuration
826  */
828 {
829  RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(global_config), ao2_cleanup);
830  int i;
831 
832  /* We can get away with this as the lifetime of the URI
833  * registered with the HTTP core is contained within
834  * the lifetime of the module configuration
835  */
836  prometheus_uri.uri = mod_cfg->general->uri;
837 
838  /* Re-register the core metrics */
839  for (i = 0; i < ARRAY_LEN(core_metrics); i++) {
840  prometheus_metric_unregister(&core_metrics[i]);
841  }
842  if (mod_cfg->general->core_metrics_enabled) {
843  char eid_str[32];
844  ast_eid_to_str(eid_str, sizeof(eid_str), &ast_eid_default);
845 
846  PROMETHEUS_METRIC_SET_LABEL(&core_scrape_metric, 0, "eid", eid_str);
847 
848  PROMETHEUS_METRIC_SET_LABEL(&core_metrics[METRIC_CORE_PROPS_ARRAY_INDEX],
849  1, "version", ast_get_version());
850  PROMETHEUS_METRIC_SET_LABEL(&core_metrics[METRIC_CORE_PROPS_ARRAY_INDEX],
851  2, "build_options", ast_get_build_opts());
852  PROMETHEUS_METRIC_SET_LABEL(&core_metrics[METRIC_CORE_PROPS_ARRAY_INDEX],
853  3, "build_date", ast_build_date);
854  PROMETHEUS_METRIC_SET_LABEL(&core_metrics[METRIC_CORE_PROPS_ARRAY_INDEX],
855  4, "build_os", ast_build_os);
856  PROMETHEUS_METRIC_SET_LABEL(&core_metrics[METRIC_CORE_PROPS_ARRAY_INDEX],
857  5, "build_kernel", ast_build_kernel);
858  PROMETHEUS_METRIC_SET_LABEL(&core_metrics[METRIC_CORE_PROPS_ARRAY_INDEX],
859  6, "build_host", ast_build_hostname);
860  snprintf(core_metrics[METRIC_CORE_PROPS_ARRAY_INDEX].value,
861  sizeof(core_metrics[METRIC_CORE_PROPS_ARRAY_INDEX].value),
862  "%d", 1);
863 
864  for (i = 0; i < ARRAY_LEN(core_metrics); i++) {
865  PROMETHEUS_METRIC_SET_LABEL(&core_metrics[i], 0, "eid", eid_str);
866  prometheus_metric_register(&core_metrics[i]);
867  }
868  }
869 }
870 
872 {
873  AST_VECTOR_APPEND(&providers, provider);
874 }
875 
876 static int unload_module(void)
877 {
879  int i;
880 
881  ast_http_uri_unlink(&prometheus_uri);
882 
883  for (i = 0; i < AST_VECTOR_SIZE(&providers); i++) {
884  const struct prometheus_metrics_provider *provider = AST_VECTOR_GET(&providers, i);
885 
886  if (!provider->unload_cb) {
887  continue;
888  }
889 
890  provider->unload_cb();
891  }
892 
893  for (i = 0; i < AST_VECTOR_SIZE(&metrics); i++) {
894  struct prometheus_metric *metric = AST_VECTOR_GET(&metrics, i);
895 
896  prometheus_metric_free(metric);
897  }
898  AST_VECTOR_FREE(&metrics);
899 
900  AST_VECTOR_FREE(&callbacks);
901 
902  AST_VECTOR_FREE(&providers);
903 
904  aco_info_destroy(&cfg_info);
906 
907  return 0;
908 }
909 
910 static int reload_module(void) {
912  int i;
913  struct prometheus_general_config *general_config;
914 
915  ast_http_uri_unlink(&prometheus_uri);
916  if (aco_process_config(&cfg_info, 1) == ACO_PROCESS_ERROR) {
917  return -1;
918  }
919 
920  /* Our config should be all reloaded now */
921  general_config = prometheus_general_config_get();
922  for (i = 0; i < AST_VECTOR_SIZE(&providers); i++) {
923  const struct prometheus_metrics_provider *provider = AST_VECTOR_GET(&providers, i);
924 
925  if (!provider->reload_cb) {
926  continue;
927  }
928 
929  if (provider->reload_cb(general_config)) {
930  ast_log(AST_LOG_WARNING, "Failed to reload metrics provider %s\n", provider->name);
931  ao2_ref(general_config, -1);
932  return -1;
933  }
934  }
935  ao2_ref(general_config, -1);
936 
937  if (ast_http_uri_link(&prometheus_uri)) {
938  ast_log(AST_LOG_WARNING, "Failed to re-register Prometheus Metrics URI during reload\n");
939  return -1;
940  }
941 
942  return 0;
943 }
944 
945 static int load_module(void)
946 {
948 
949  if (AST_VECTOR_INIT(&metrics, 64)) {
950  goto cleanup;
951  }
952 
953  if (AST_VECTOR_INIT(&callbacks, 8)) {
954  goto cleanup;
955  }
956 
957  if (AST_VECTOR_INIT(&providers, 8)) {
958  goto cleanup;
959  }
960 
961  if (aco_info_init(&cfg_info)) {
962  goto cleanup;
963  }
964  aco_option_register(&cfg_info, "enabled", ACO_EXACT, global_options, "no", OPT_BOOL_T, 1, FLDSET(struct prometheus_general_config, enabled));
965  aco_option_register(&cfg_info, "core_metrics_enabled", ACO_EXACT, global_options, "yes", OPT_BOOL_T, 1, FLDSET(struct prometheus_general_config, core_metrics_enabled));
966  aco_option_register(&cfg_info, "uri", ACO_EXACT, global_options, "", OPT_STRINGFIELD_T, 1, STRFLDSET(struct prometheus_general_config, uri));
967  aco_option_register(&cfg_info, "auth_username", ACO_EXACT, global_options, "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct prometheus_general_config, auth_username));
968  aco_option_register(&cfg_info, "auth_password", ACO_EXACT, global_options, "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct prometheus_general_config, auth_password));
969  aco_option_register(&cfg_info, "auth_realm", ACO_EXACT, global_options, "Asterisk Prometheus Metrics", OPT_STRINGFIELD_T, 0, STRFLDSET(struct prometheus_general_config, auth_realm));
970  if (aco_process_config(&cfg_info, 0) == ACO_PROCESS_ERROR) {
971  goto cleanup;
972  }
973 
974  if (cli_init()
977  || bridge_metrics_init()) {
978  goto cleanup;
979  }
980 
981  if(ast_module_check("res_pjsip_outbound_registration.so")) {
982  /* Call a local function, used in the core prometheus code only */
984  goto cleanup;
985  }
986 
987  if (ast_http_uri_link(&prometheus_uri)) {
988  goto cleanup;
989  }
990 
992 
993 cleanup:
994  ast_http_uri_unlink(&prometheus_uri);
995  aco_info_destroy(&cfg_info);
996  AST_VECTOR_FREE(&metrics);
997  AST_VECTOR_FREE(&callbacks);
998  AST_VECTOR_FREE(&providers);
999 
1000  return AST_MODULE_LOAD_DECLINE;
1001 }
1002 
1003 
1004 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "Asterisk Prometheus Module",
1005  .support_level = AST_MODULE_SUPPORT_EXTENDED,
1006  .load = load_module,
1007  .unload = unload_module,
1008  .reload = reload_module,
1009  .load_pri = AST_MODPRI_DEFAULT,
1010 #ifdef HAVE_PJPROJECT
1011  /* This module explicitly calls into res_pjsip if Asterisk is built with PJSIP support, so they are required. */
1012  .requires = "res_pjsip",
1013  .optional_modules = "res_pjsip_outbound_registration",
1014 #endif
1015 );
#define AST_VECTOR_FREE(vec)
Deallocates this vector.
Definition: vector.h:174
struct prometheus_metric * prometheus_counter_create(const char *name, const char *help)
Create a malloc'd counter metric.
int prometheus_metric_unregister(struct prometheus_metric *metric)
Remove a registered metric.
Asterisk main include file. File version handling, generic pbx functions.
An actual, honest to god, metric.
char * userid
Definition: http.h:127
void(*const unload_cb)(void)
Unload callback.
int pjsip_outbound_registration_metrics_init(void)
Initialize PJSIP outbound registration metrics.
void prometheus_metrics_provider_register(const struct prometheus_metrics_provider *provider)
Register a metrics provider.
Asterisk version information.
char * ast_eid_to_str(char *s, int maxlen, struct ast_eid *eid)
Convert an EID to a string.
Definition: utils.c:2839
Prometheus general configuration.
Prometheus Metric Internal API.
struct prometheus_general_config * prometheus_general_config_get(void)
Retrieve the current configuration of the module.
const char * ast_get_version(void)
Retrieve the Asterisk version string.
Definition: version.c:18
int64_t ast_tvdiff_sec(struct timeval end, struct timeval start)
Computes the difference (in seconds) between two struct timeval instances.
Definition: time.h:73
int ast_http_uri_link(struct ast_http_uri *urihandler)
Register a URI handler.
Definition: http.c:676
#define aco_option_register(info, name, matchtype, types, default_val, opt_type, flags,...)
Register a config option.
A metric whose value always goes up.
const char * ast_get_build_opts(void)
Definition: version.c:28
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
const char * name
The name of our callback (always useful for debugging)
enum prometheus_metric_allocation_strategy allocation_strategy
How this metric was allocated.
void ast_http_uri_unlink(struct ast_http_uri *urihandler)
Unregister a URI handler.
Definition: http.c:708
enum prometheus_metric_type type
What type of metric we are.
int ast_str_append(struct ast_str **buf, ssize_t max_len, const char *fmt,...)
Append to a thread local dynamic string.
Definition: strings.h:1139
enum aco_process_status aco_process_config(struct aco_info *info, int reload)
Process a config info via the options registered with an aco_info.
#define ao2_global_obj_ref(holder)
Get a reference to the object stored in the global holder.
Definition: astobj2.h:918
struct timeval ast_tvnow(void)
Returns current timeval. Meant to replace calls to gettimeofday().
Definition: time.h:159
void * prometheus_general_config_alloc(void)
Allocate a new configuration object.
void(* get_metric_value)(struct prometheus_metric *metric)
Callback function to obtain the metric value.
int endpoint_metrics_init(void)
Initialize endpoint metrics.
int64_t ast_tvdiff_ms(struct timeval end, struct timeval start)
Computes the difference (in milliseconds) between two struct timeval instances.
Definition: time.h:107
The representation of a single configuration file to be processed.
void prometheus_callback_unregister(struct prometheus_callback *callback)
Remove a registered callback.
enum aco_type_t type
void ast_http_send(struct ast_tcptls_session_instance *ser, enum ast_http_method method, int status_code, const char *status_title, struct ast_str *http_header, struct ast_str *out, int fd, unsigned int static_content)
Generic function for sending HTTP/1.1 response.
Definition: http.c:459
#define AST_LIST_TRAVERSE_SAFE_END
Closes a safe loop traversal block.
Definition: linkedlists.h:615
#define ACO_TYPES(...)
A helper macro to ensure that aco_info types always have a sentinel.
int cli_init(void)
Initialize CLI command.
static void module_config_dtor(void *obj)
Configuration object destructor.
#define PROMETHEUS_METRIC_STATIC_INITIALIZATION(mtype, n, h, cb)
Convenience macro for initializing a metric on the stack.
int ast_module_check(const char *name)
Check if module with the name given is loaded.
Definition: loader.c:2823
The metric was allocated on the stack.
#define ao2_bump(obj)
Bump refcount on an AO2 object by one, returning the object.
Definition: astobj2.h:480
Support for Private Asterisk HTTP Servers.
static void cleanup(void)
Clean up any old apps that we don't need any more.
Definition: res_stasis.c:327
#define FLDSET(type,...)
Convert a struct and list of fields to an argument list of field offsets.
#define AST_VECTOR_INIT(vec, size)
Initialize a vector.
Definition: vector.h:113
void(* callback_fn)(struct ast_str **output)
The callback function to invoke.
ast_mutex_t lock
int aco_info_init(struct aco_info *info)
Initialize an aco_info structure.
#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:589
void * aco_pending_config(struct aco_info *info)
Get pending config changes.
#define PROMETHEUS_MAX_LABELS
How many labels a single metric can have.
#define ast_string_field_init(x, size)
Initialize a field pool and fields.
Definition: stringfields.h:359
#define ao2_ref(o, delta)
Reference/unreference an object and return the old refcount.
Definition: astobj2.h:459
struct prometheus_metric::@274 children
A list of children metrics.
#define AST_LIST_REMOVE_CURRENT(field)
Removes the current entry from a list during a traversal.
Definition: linkedlists.h:557
char name[PROMETHEUS_MAX_NAME_LENGTH]
The name of the label.
The configuration settings for this module.
Definition: cdr.c:264
#define ast_debug(level,...)
Log a DEBUG message.
#define AST_VECTOR(name, type)
Define a vector structure.
Definition: vector.h:44
#define AST_LIST_REMOVE_HEAD(head, field)
Removes and returns the head entry from a list.
Definition: linkedlists.h:833
describes a server instance
Definition: tcptls.h:150
Their was an error and no changes were applied.
int64_t prometheus_last_scrape_duration_get(void)
Retrieve the amount of time it took to perform the last scrape.
Configuration option-handling.
static int prometheus_config_pre_apply(void)
Pre-apply callback for the config framework.
Asterisk Prometheus Metrics.
#define AST_LIST_INSERT_TAIL(head, elm, field)
Appends a list entry to the tail of a list.
Definition: linkedlists.h:731
Support for dynamic strings.
Definition: strings.h:623
int prometheus_metric_registered_count(void)
void aco_info_destroy(struct aco_info *info)
Destroy an initialized aco_info struct.
#define ao2_global_obj_release(holder)
Release the ao2 object held in the global holder.
Definition: astobj2.h:859
int channel_metrics_init(void)
Initialize channel metrics.
Definition: channels.c:241
Type for default option handler for bools (ast_true/ast_false)
struct ast_str * prometheus_scrape_to_string(void)
Get the raw output of what a scrape would produce.
struct prometheus_label labels[PROMETHEUS_MAX_LABELS]
The metric's labels.
int(*const reload_cb)(struct prometheus_general_config *config)
Reload callback.
char value[PROMETHEUS_MAX_VALUE_LENGTH]
The current value.
char * password
Definition: http.h:129
#define AST_LIST_TRAVERSE(head, var, field)
Loops over (traverses) the entries in a list.
Definition: linkedlists.h:491
const char * help
Pointer to a static string defining this metric's help text.
char value[PROMETHEUS_MAX_LABEL_LENGTH]
The value of the label.
#define ast_calloc(num, len)
A wrapper for calloc()
Definition: astmm.h:202
int prometheus_metric_register(struct prometheus_metric *metric)
static AO2_GLOBAL_OBJ_STATIC(global_config)
The module configuration container.
#define STRFLDSET(type,...)
Convert a struct and a list of stringfield fields to an argument list of field offsets.
Module has failed to load, may be in an inconsistent state.
Definition: module.h:78
Vector container support.
static struct aco_type global_option
An aco_type structure to link the "general" category to the skel_global_config type.
Definition: app_skel.c:241
static struct prometheus_metric core_metrics[]
Core metrics to scrape.
struct prometheus_general_config * general
General settings.
const char * name
Handy name of the provider for debugging purposes.
char name[PROMETHEUS_MAX_NAME_LENGTH]
Our metric name.
struct ast_eid ast_eid_default
Global EID.
Definition: options.c:93
static ast_mutex_t scrape_lock
Lock that protects data structures during an HTTP scrape.
void prometheus_metric_to_string(struct prometheus_metric *metric, struct ast_str **output)
Convert a metric (and its children) into Prometheus compatible text.
#define AST_VECTOR_GET(vec, idx)
Get an element from a vector.
Definition: vector.h:680
HTTP authentication information.
Definition: http.h:125
#define ao2_replace(dst, src)
Replace one object reference with another cleaning up the original.
Definition: astobj2.h:501
Type information about a category-level configurable object.
static int enabled
Whether or not we are storing history.
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
Definition: strings.h:425
const char * filename
A function table for a metrics provider.
Definition of a URI handler.
Definition: http.h:102
static void * module_config_alloc(void)
Module config constructor.
ast_mutex_t lock
A lock protecting the metric value.
static void prometheus_config_post_apply(void)
Post-apply callback for the config framework.
struct ast_http_auth * ast_http_get_auth(struct ast_variable *headers)
Get HTTP authentication information from headers.
Definition: http.c:1582
prometheus_metric_type
Prometheus metric type.
#define AST_VECTOR_INSERT_AT(vec, idx, elem)
Insert an element at a specific position in a vector, growing the vector if needed.
Definition: vector.h:338
Type for default option handler for stringfields.
A metric whose value can bounce around like a jackrabbit.
Definition: search.h:40
#define AST_LIST_TRAVERSE_SAFE_BEGIN(head, var, field)
Loops safely over (traverses) the entries in a list.
Definition: linkedlists.h:529
The metric was allocated on the heap.
ast_http_method
HTTP Request methods known by Asterisk.
Definition: http.h:58
void prometheus_metric_free(struct prometheus_metric *metric)
Destroy a metric and all its children.
int prometheus_callback_register(struct prometheus_callback *callback)
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
struct ast_cdr_config * general
Definition: cdr.c:265
#define AST_VECTOR_REMOVE(vec, idx, preserve_ordered)
Remove an element from a vector by index.
Definition: vector.h:412
Asterisk module definitions.
#define RAII_VAR(vartype, varname, initval, dtor)
Declare a variable that will call a destructor function when it goes out of scope.
Definition: utils.h:941
struct prometheus_metric * prometheus_gauge_create(const char *name, const char *help)
Create a malloc'd gauge metric.
#define ast_string_field_free_memory(x)
free all memory - to be called before destroying the object
Definition: stringfields.h:374
static struct prometheus_metric core_scrape_metric
The scrape duration metric.
Defines a callback that will be invoked when the HTTP route is called.
void prometheus_general_config_set(struct prometheus_general_config *config)
Set the configuration for the module.
#define AST_VECTOR_SIZE(vec)
Get the number of elements in a vector.
Definition: vector.h:609
struct timeval prometheus_last_scrape_time_get(void)
Retrieve the timestamp when the last scrape occurred.
CONFIG_INFO_STANDARD(cfg_info, global_config, module_config_alloc,.files=ACO_FILES(&prometheus_conf),.pre_apply_config=prometheus_config_pre_apply,.post_apply_config=prometheus_config_post_apply,)
Register information about the configs being processed by this module.
int bridge_metrics_init(void)
Initialize bridge metrics.
Definition: bridges.c:206
#define ast_str_create(init_len)
Create a malloc'ed dynamic length string.
Definition: strings.h:659