Asterisk - The Open Source Telephony Project  GIT-master-44aef04
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  <support_level>extended</support_level>
31  ***/
32 
33 /*** DOCUMENTATION
34  <configInfo name="res_prometheus" language="en_US">
35  <synopsis>Resource for integration with Prometheus</synopsis>
36  <configFile name="prometheus.conf">
37  <configObject name="general">
38  <synopsis>General settings.</synopsis>
39  <description>
40  <para>
41  The <emphasis>general</emphasis> settings section contains information
42  to configure Asterisk to serve up statistics for a Prometheus server.
43  </para>
44  <note>
45  <para>You must enable Asterisk's HTTP server in <filename>http.conf</filename>
46  for this module to function properly!
47  </para>
48  </note>
49  </description>
50  <configOption name="enabled" default="no">
51  <synopsis>Enable or disable Prometheus statistics.</synopsis>
52  <description>
53  <enumlist>
54  <enum name="no" />
55  <enum name="yes" />
56  </enumlist>
57  </description>
58  </configOption>
59  <configOption name="core_metrics_enabled" default="yes">
60  <synopsis>Enable or disable core metrics.</synopsis>
61  <description>
62  <para>
63  Core metrics show various properties of the Asterisk system, including
64  how the binary was built, the version, uptime, last reload time, etc.
65  Generally, these options are harmless and should always be enabled.
66  This option mostly exists to disable output of all options for testing
67  purposes, as well as for those foolish souls who really don't care
68  what version of Asterisk they're running.
69  </para>
70  <enumlist>
71  <enum name="no" />
72  <enum name="yes" />
73  </enumlist>
74  </description>
75  </configOption>
76  <configOption name="uri" default="metrics">
77  <synopsis>The HTTP URI to serve metrics up on.</synopsis>
78  </configOption>
79  <configOption name="auth_username">
80  <synopsis>Username to use for Basic Auth.</synopsis>
81  <description>
82  <para>
83  If set, use Basic Auth to authenticate requests to the route
84  specified by <replaceable>uri</replaceable>. Note that you
85  will need to configure your Prometheus server with the
86  appropriate auth credentials.
87  </para>
88  <para>
89  If set, <replaceable>auth_password</replaceable> must also
90  be set appropriately.
91  </para>
92  <warning>
93  <para>
94  It is highly recommended to set up Basic Auth. Failure
95  to do so may result in useful information about your
96  Asterisk system being made easily scrapable by the
97  wide world. Consider yourself duly warned.
98  </para>
99  </warning>
100  </description>
101  </configOption>
102  <configOption name="auth_password">
103  <synopsis>Password to use for Basic Auth.</synopsis>
104  <description>
105  <para>
106  If set, this is used in conjunction with <replaceable>auth_username</replaceable>
107  to require Basic Auth for all requests to the Prometheus metrics. Note that
108  setting this without <replaceable>auth_username</replaceable> will not
109  do anything.
110  </para>
111  </description>
112  </configOption>
113  <configOption name="auth_realm" default="Asterisk Prometheus Metrics">
114  <synopsis>Auth realm used in challenge responses</synopsis>
115  </configOption>
116  </configObject>
117  </configFile>
118  </configInfo>
119 ***/
120 
121 #define AST_MODULE_SELF_SYM __internal_res_prometheus_self
122 
123 #include "asterisk.h"
124 
125 #include "asterisk/module.h"
126 #include "asterisk/vector.h"
127 #include "asterisk/http.h"
128 #include "asterisk/config_options.h"
129 #include "asterisk/ast_version.h"
130 #include "asterisk/buildinfo.h"
131 #include "asterisk/res_prometheus.h"
132 
134 
135 /*! \brief Lock that protects data structures during an HTTP scrape */
137 
138 AST_VECTOR(, struct prometheus_metric *) metrics;
139 
140 AST_VECTOR(, struct prometheus_callback *) callbacks;
141 
143 
144 static struct timeval last_scrape;
145 
146 /*! \brief The actual module config */
147 struct module_config {
148  /*! \brief General settings */
150 };
151 
152 static struct aco_type global_option = {
153  .type = ACO_GLOBAL,
154  .name = "general",
155  .item_offset = offsetof(struct module_config, general),
156  .category_match = ACO_WHITELIST_EXACT,
157  .category = "general",
158 };
159 
160 struct aco_type *global_options[] = ACO_TYPES(&global_option);
161 
163  .filename = "prometheus.conf",
164  .types = ACO_TYPES(&global_option),
165 };
166 
167 /*! \brief The module configuration container */
169 
170 static void *module_config_alloc(void);
171 static int prometheus_config_pre_apply(void);
172 static void prometheus_config_post_apply(void);
173 /*! \brief Register information about the configs being processed by this module */
175  .files = ACO_FILES(&prometheus_conf),
176  .pre_apply_config = prometheus_config_pre_apply,
177  .post_apply_config = prometheus_config_post_apply,
178 );
179 
180 #define CORE_PROPERTIES_HELP "Asterisk instance properties. The value of this will always be 1."
181 
182 #define CORE_UPTIME_HELP "Asterisk instance uptime in seconds."
183 
184 #define CORE_LAST_RELOAD_HELP "Time since last Asterisk reload in seconds."
185 
186 #define CORE_METRICS_SCRAPE_TIME_HELP "Total time taken to collect metrics, in milliseconds"
187 
188 static void get_core_uptime_cb(struct prometheus_metric *metric)
189 {
190  struct timeval now = ast_tvnow();
191  int64_t duration = ast_tvdiff_sec(now, ast_startuptime);
192 
193  snprintf(metric->value, sizeof(metric->value), "%" PRIu64, duration);
194 }
195 
196 static void get_last_reload_cb(struct prometheus_metric *metric)
197 {
198  struct timeval now = ast_tvnow();
199  int64_t duration = ast_tvdiff_sec(now, ast_lastreloadtime);
200 
201  snprintf(metric->value, sizeof(metric->value), "%" PRIu64, duration);
202 }
203 
204 /*!
205  * \brief The scrape duration metric
206  *
207  * \details
208  * This metric is special in that it should never be registered.
209  * Instead, the HTTP callback function that walks the metrics will
210  * always populate this metric explicitly if core metrics
211  * are enabled.
212  */
216  "asterisk_core_scrape_time_ms",
218  NULL);
219 
220 #define METRIC_CORE_PROPS_ARRAY_INDEX 0
221 /*!
222  * \brief Core metrics to scrape
223  */
224 static struct prometheus_metric core_metrics[] = {
227  "asterisk_core_properties",
229  NULL),
232  "asterisk_core_uptime_seconds",
237  "asterisk_core_last_reload_seconds",
240 };
241 
242 /**
243  * \internal
244  * \brief Compare two metrics to see if their name / labels / values match
245  *
246  * \param left The first metric to compare
247  * \param right The second metric to compare
248  *
249  * \retval 0 The metrics are not the same
250  * \retval 1 The metrics are the same
251  */
252 static int prometheus_metric_cmp(struct prometheus_metric *left,
253  struct prometheus_metric *right)
254 {
255  int i;
256  ast_debug(5, "Comparison: Names %s == %s\n", left->name, right->name);
257  if (strcmp(left->name, right->name)) {
258  return 0;
259  }
260 
261  for (i = 0; i < PROMETHEUS_MAX_LABELS; i++) {
262  ast_debug(5, "Comparison: Label %d Names %s == %s\n", i,
263  left->labels[i].name, right->labels[i].name);
264  if (strcmp(left->labels[i].name, right->labels[i].name)) {
265  return 0;
266  }
267 
268  ast_debug(5, "Comparison: Label %d Values %s == %s\n", i,
269  left->labels[i].value, right->labels[i].value);
270  if (strcmp(left->labels[i].value, right->labels[i].value)) {
271  return 0;
272  }
273  }
274 
275  ast_debug(5, "Copmarison: %s (%p) is equal to %s (%p)\n",
276  left->name, left, right->name, right);
277  return 1;
278 }
279 
281 {
283 
284  return AST_VECTOR_SIZE(&metrics);
285 }
286 
288 {
290  int i;
291 
292  if (!metric) {
293  return -1;
294  }
295 
296  for (i = 0; i < AST_VECTOR_SIZE(&metrics); i++) {
297  struct prometheus_metric *existing = AST_VECTOR_GET(&metrics, i);
298  struct prometheus_metric *child;
299 
300  if (prometheus_metric_cmp(existing, metric)) {
302  "Refusing registration of existing Prometheus metric: %s\n",
303  metric->name);
304  return -1;
305  }
306 
307  AST_LIST_TRAVERSE(&existing->children, child, entry) {
308  if (prometheus_metric_cmp(child, metric)) {
310  "Refusing registration of existing Prometheus metric: %s\n",
311  metric->name);
312  return -1;
313  }
314  }
315 
316  if (!strcmp(metric->name, existing->name)) {
317  ast_debug(3, "Nesting metric '%s' as child (%p) under existing (%p)\n",
318  metric->name, metric, existing);
319  AST_LIST_INSERT_TAIL(&existing->children, metric, entry);
320  return 0;
321  }
322  }
323 
324  ast_debug(3, "Tracking new root metric '%s'\n", metric->name);
325  if (AST_VECTOR_APPEND(&metrics, metric)) {
326  ast_log(AST_LOG_WARNING, "Failed to grow vector to make room for Prometheus metric: %s\n",
327  metric->name);
328  return -1;
329  }
330 
331  return 0;
332 }
333 
335 {
336  if (!metric) {
337  return -1;
338  }
339 
340  {
342  int i;
343 
344  ast_debug(3, "Removing metric '%s'\n", metric->name);
345  for (i = 0; i < AST_VECTOR_SIZE(&metrics); i++) {
346  struct prometheus_metric *existing = AST_VECTOR_GET(&metrics, i);
347 
348  /*
349  * If this is a complete match, remove the matching metric
350  * and place its children back into the list
351  */
352  if (prometheus_metric_cmp(existing, metric)) {
353  struct prometheus_metric *root;
354 
355  AST_VECTOR_REMOVE(&metrics, i, 1);
356  root = AST_LIST_REMOVE_HEAD(&existing->children, entry);
357  if (root) {
358  struct prometheus_metric *child;
359  AST_LIST_TRAVERSE_SAFE_BEGIN(&existing->children, child, entry) {
361  AST_LIST_INSERT_TAIL(&root->children, child, entry);
362  }
364  AST_VECTOR_INSERT_AT(&metrics, i, root);
365  }
366  prometheus_metric_free(existing);
367  return 0;
368  }
369 
370  /*
371  * Name match, but labels don't match. Find the matching entry with
372  * labels and remove it along with all of its children
373  */
374  if (!strcmp(existing->name, metric->name)) {
375  struct prometheus_metric *child;
376 
377  AST_LIST_TRAVERSE_SAFE_BEGIN(&existing->children, child, entry) {
378  if (prometheus_metric_cmp(child, metric)) {
380  prometheus_metric_free(child);
381  return 0;
382  }
383  }
385  }
386  }
387  }
388 
389  return -1;
390 }
391 
393 {
394  struct prometheus_metric *child;
395 
396  if (!metric) {
397  return;
398  }
399 
400  while ((child = AST_LIST_REMOVE_HEAD(&metric->children, entry))) {
401  prometheus_metric_free(child);
402  }
403  ast_mutex_destroy(&metric->lock);
404 
406  return;
407  } else if (metric->allocation_strategy == PROMETHEUS_METRIC_MALLOCD) {
408  ast_free(metric);
409  }
410 }
411 
412 /**
413  * \internal
414  * \brief Common code for creating a metric
415  *
416  * \param name The name of the metric
417  * \param help Help string to output when rendered. This must be static.
418  *
419  * \retval \c prometheus_metric on success
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 
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  */
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,
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 
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 {
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 
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  }
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 
695  scrape_metrics(&response);
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 {
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 {
741 
742  if (!mod_cfg) {
743  return NULL;
744  }
745  ao2_bump(mod_cfg->general);
746 
747  return mod_cfg->general;
748 }
749 
751 {
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 {
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 
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++) {
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 
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++) {
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()
979  goto cleanup;
980  }
981 
982  if (ast_http_uri_link(&prometheus_uri)) {
983  goto cleanup;
984  }
985 
987 
988 cleanup:
989  ast_http_uri_unlink(&prometheus_uri);
990  aco_info_destroy(&cfg_info);
991  AST_VECTOR_FREE(&metrics);
992  AST_VECTOR_FREE(&callbacks);
994 
996 }
997 
998 
1000  .support_level = AST_MODULE_SUPPORT_EXTENDED,
1001  .load = load_module,
1002  .unload = unload_module,
1003  .reload = reload_module,
1004  .load_pri = AST_MODPRI_DEFAULT,
1005 #ifdef HAVE_PJPROJECT
1006  .requires = "res_pjsip",
1007 #endif
1008 );
#define AST_VECTOR_FREE(vec)
Deallocates this vector.
Definition: vector.h:174
static const char type[]
Definition: chan_ooh323.c:109
struct prometheus_metric * prometheus_counter_create(const char *name, const char *help)
Create a malloc&#39;d counter metric.
static int http_callback(struct ast_tcptls_session_instance *ser, const struct ast_http_uri *urih, const char *uri, enum ast_http_method method, struct ast_variable *get_params, struct ast_variable *headers)
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.
#define ARRAY_LEN(a)
Definition: isdn_lib.c:42
static int unload_module(void)
char * userid
Definition: http.h:125
void(*const unload_cb)(void)
Unload callback.
int pjsip_outbound_registration_metrics_init(void)
Initialize PJSIP outbound registration metrics.
char * config
Definition: conf2ael.c:66
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: main/utils.c:2458
Prometheus general configuration.
struct prometheus_general_config * prometheus_general_config_get(void)
Retrieve the current configuration of the module.
#define METRIC_CORE_PROPS_ARRAY_INDEX
const char * ast_get_version(void)
Retrieve the Asterisk version string.
int64_t ast_tvdiff_sec(struct timeval end, struct timeval start)
Computes the difference (in seconds) between two struct timeval instances.
Definition: time.h:64
int ast_http_uri_link(struct ast_http_uri *urihandler)
Register a URI handler.
Definition: http.c:673
#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_build_date
Definition: buildinfo.c:33
const char * ast_get_build_opts(void)
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
#define AST_LOG_WARNING
Definition: logger.h:279
const char * name
The name of our callback (always useful for debugging)
const char * ast_build_os
Definition: buildinfo.c:32
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:705
#define CORE_UPTIME_HELP
static struct ast_http_uri prometheus_uri
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:1091
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)
Definition: astobj2.h:925
struct timeval ast_tvnow(void)
Returns current timeval. Meant to replace calls to gettimeofday().
Definition: time.h:150
void * prometheus_general_config_alloc(void)
Allocate a new configuration object.
void(* get_metric_value)(struct prometheus_metric *metric)
#define ast_assert(a)
Definition: utils.h:650
int endpoint_metrics_init(void)
Initialize endpoint metrics.
#define ast_mutex_lock(a)
Definition: lock.h:187
int64_t ast_tvdiff_ms(struct timeval end, struct timeval start)
Computes the difference (in milliseconds) between two struct timeval instances.
Definition: time.h:98
#define NULL
Definition: resample.c:96
The representation of a single configuration file to be processed.
int value
Definition: syslog.c:37
static void get_core_uptime_cb(struct prometheus_metric *metric)
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:456
#define AST_LIST_TRAVERSE_SAFE_END
Closes a safe loop traversal block.
Definition: linkedlists.h:614
#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.
const char * ast_build_hostname
Definition: buildinfo.c:29
static void prometheus_metric_full_to_string(struct prometheus_metric *metric, struct ast_str **output)
const char * ast_build_kernel
Definition: buildinfo.c:30
The metric was allocated on the stack.
#define ao2_bump(obj)
Definition: astobj2.h:491
#define AST_LOG_NOTICE
Definition: logger.h:268
Support for Private Asterisk HTTP Servers.
#define ast_debug(level,...)
Log a DEBUG message.
Definition: logger.h:444
#define ast_log
Definition: astobj2.c:42
#define AST_LOG_ERROR
Definition: logger.h:290
#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.
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:587
void * aco_pending_config(struct aco_info *info)
Get pending config changes.
#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:851
#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:353
static void get_last_reload_cb(struct prometheus_metric *metric)
ast_mutex_t lock
Definition: app_meetme.c:1091
#define AST_LIST_REMOVE_CURRENT(field)
Removes the current entry from a list during a traversal.
Definition: linkedlists.h:556
#define ao2_ref(o, delta)
Definition: astobj2.h:464
char name[PROMETHEUS_MAX_NAME_LENGTH]
The name of the label.
struct ao2_container * providers
struct timeval ast_lastreloadtime
Definition: asterisk.c:337
const char * method
Definition: res_pjsip.c:4220
The configuration settings for this module.
Definition: cdr.c:222
#define AST_LIST_REMOVE_HEAD(head, field)
Removes and returns the head entry from a list.
Definition: linkedlists.h:832
struct aco_file prometheus_conf
describes a server instance
Definition: tcptls.h:149
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 struct prometheus_metric * prometheus_metric_create(const char *name, const char *help)
static int prometheus_config_pre_apply(void)
Pre-apply callback for the config framework.
#define AST_LIST_INSERT_TAIL(head, elm, field)
Appends a list entry to the tail of a list.
Definition: linkedlists.h:730
The descriptor of a dynamic string XXX storage will be optimized later if needed We use the ts field ...
Definition: strings.h:584
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)
Definition: astobj2.h:865
int channel_metrics_init(void)
Initialize channel metrics.
Definition: channels.c:241
Type for default option handler for bools (ast_true/ast_false)
static struct aco_type global_option
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&#39;s labels.
const char * description
Definition: http.h:102
int(*const reload_cb)(struct prometheus_general_config *config)
Reload callback.
#define ao2_alloc(data_size, destructor_fn)
Definition: astobj2.h:411
char value[PROMETHEUS_MAX_VALUE_LENGTH]
The current value.
struct prometheus_metric::@306 children
A list of children metrics.
char * password
Definition: http.h:127
struct aco_type * global_options[]
#define AST_LIST_TRAVERSE(head, var, field)
Loops over (traverses) the entries in a list.
Definition: linkedlists.h:490
#define ast_strlen_zero(a)
Definition: muted.c:73
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
char value[PROMETHEUS_MAX_LABEL_LENGTH]
The value of the label.
#define ast_calloc(num, len)
A wrapper for calloc()
Definition: astmm.h:204
static int reload(void)
Definition: cdr_mysql.c:741
int prometheus_metric_register(struct prometheus_metric *metric)
static AO2_GLOBAL_OBJ_STATIC(global_config)
The module configuration container.
AST_VECTOR(struct prometheus_metric *)
The actual module config.
#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 int reload_module(void)
static struct prometheus_metric core_metrics[]
Core metrics to scrape.
static void * cleanup(void *unused)
Definition: pbx_realtime.c:124
const char * name
Handy name of the provider for debugging purposes.
AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS|AST_MODFLAG_LOAD_ORDER, "HTTP Phone Provisioning",.support_level=AST_MODULE_SUPPORT_EXTENDED,.load=load_module,.unload=unload_module,.reload=reload,.load_pri=AST_MODPRI_CHANNEL_DEPEND,.requires="http",)
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.
struct timeval ast_startuptime
Definition: asterisk.c:336
#define ACO_FILES(...)
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:682
static int load_module(void)
HTTP authentication information.
Definition: http.h:123
#define ao2_replace(dst, src)
Definition: astobj2.h:517
#define ao2_cleanup(obj)
Definition: astobj2.h:1958
static int prometheus_metric_cmp(struct prometheus_metric *left, struct prometheus_metric *right)
Type information about a category-level configurable object.
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
Definition: strings.h:401
const char * filename
A function table for a metrics provider.
Definition of a URI handler.
Definition: http.h:100
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.
#define CORE_METRICS_SCRAPE_TIME_HELP
struct ast_http_auth * ast_http_get_auth(struct ast_variable *headers)
Get HTTP authentication information from headers.
Definition: http.c:1579
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.
static void prometheus_general_config_dtor(void *obj)
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:528
The metric was allocated on the heap.
#define ast_mutex_init(pmutex)
Definition: lock.h:184
const char * uri
Definition: http.h:103
ast_http_method
HTTP Request methods known by Asterisk.
Definition: http.h:56
#define ast_mutex_destroy(a)
Definition: lock.h:186
void prometheus_metric_free(struct prometheus_metric *metric)
Destroy a metric and all its children.
static void scrape_metrics(struct ast_str **response)
int prometheus_callback_register(struct prometheus_callback *callback)
static const char * prometheus_metric_type_to_string(enum prometheus_metric_type type)
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
struct ast_cdr_config * general
Definition: cdr.c:223
#define AST_VECTOR_REMOVE(vec, idx, preserve_ordered)
Remove an element from a vector by index.
Definition: vector.h:412
Asterisk module definitions.
struct prometheus_metric * prometheus_gauge_create(const char *name, const char *help)
Create a malloc&#39;d gauge metric.
static struct prometheus_metrics_provider provider
Definition: bridges.c:178
#define ast_string_field_free_memory(x)
free all memory - to be called before destroying the object
Definition: stringfields.h:368
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:611
struct timeval prometheus_last_scrape_time_get(void)
Retrieve the timestamp when the last scrape occurred.
#define AST_MUTEX_DEFINE_STATIC(mutex)
Definition: lock.h:518
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.
#define CORE_LAST_RELOAD_HELP
int bridge_metrics_init(void)
Initialize bridge metrics.
Definition: bridges.c:183
#define ast_str_create(init_len)
Create a malloc&#39;ed dynamic length string.
Definition: strings.h:620
#define ast_mutex_unlock(a)
Definition: lock.h:188
#define CORE_PROPERTIES_HELP
static int enabled
Definition: dnsmgr.c:91