From 9033133d1a22ceac50f4090b0ce825ee24d0ea09 Mon Sep 17 00:00:00 2001 From: Nick Williams Date: Tue, 15 Oct 2019 14:22:46 -0500 Subject: [PATCH] Align with stock statsd plugin, add extra tags, add metric whitelisting (#1) - After this plugin was created, the stock statsd plugin in the uWSGI repo was updated to support disabling worker metrics and forcing all metrics to be gauges. The first change this commit makes is to port those two changes into this plugin. - We only use 3-4 metrics of the dozens that uWSGI records. I've added a whitelisting feature that enables us to filter out undesired metrics, reducing our use of Datadog custom metrics. - We need the ability to specify additional tags, so that we can more easily graph uWSGI metrics belonging to specific applications (or group uWSGI metrics by application). I've added that capability here. This change is completely backwards compatible. If a user has a config such as the following, the plugin behavior will continue in the exact same manner as before this change: ``` stats-push=dogstatsd:localhost:8135,uwsgi ``` Now, however, we can add more advanced configuration options, like below, to alter the plugin behavior: ``` stats-push=dogstatsd:localhost:8135,uwsgi dogstatsd-extra-tags=app:foo_service dogstatsd-no-workers=true dogstatsd-whitelist-metric=core.busy_workers dogstatsd-whitelist-metric=core.idle_workers dogstatsd-whitelist-metric=core.overloaded dogstatsd-whitelist-metric=socket.listen_queue ``` --- README.md | 13 +++++++++++++ plugin.c | 43 +++++++++++++++++++++++++++++++++++++++---- 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 2255b68..3edf3b7 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,19 @@ wsgi-file = app.py ... ``` +You can also add additional tags or filter which metrics are published (or how they are published) using one or more optional configuration options: + +```ini +stats-push = dogstatsd:127.0.0.1:8125,myapp +dogstatsd-extra-tags = app:foo_service,instance:1 +dogstatsd-no-workers = true +dogstatsd-all_gauges = true +dogstatsd-whitelist-metric = core.busy_workers +dogstatsd-whitelist-metric = core.idle_workers +dogstatsd-whitelist-metric = core.overloaded +dogstatsd-whitelist-metric = socket.listen_queue +``` + This will begin producing metrics with the prefix defined in the configuration, `myapp` here: ```console diff --git a/plugin.c b/plugin.c index 2d6d3d8..dde9f0b 100644 --- a/plugin.c +++ b/plugin.c @@ -18,6 +18,21 @@ exports values exposed by the metric subsystem to a Datadog Agent StatsD server extern struct uwsgi_server uwsgi; +struct dogstatsd_config { + int no_workers; + int all_gauges; + char *extra_tags; + struct uwsgi_string_list *metrics_whitelist; +} u_dogstatsd_config; + +static struct uwsgi_option dogstatsd_options[] = { + {"dogstatsd-no-workers", no_argument, 0, "disable generation of single-worker metrics", uwsgi_opt_true, &u_dogstatsd_config.no_workers, 0}, + {"dogstatsd-all-gauges", no_argument, 0, "push all metrics to dogstatsd as gauges", uwsgi_opt_true, &u_dogstatsd_config.all_gauges, 0}, + {"dogstatsd-extra-tags", required_argument, 0, "add these extra tags to all metrics (example: foo:bar,qin,baz:qux)", uwsgi_opt_set_str, &u_dogstatsd_config.extra_tags, 0}, + {"dogstatsd-whitelist-metric", required_argument, 0, "use one or more times to send only the whitelisted metrics (do not add prefix)", uwsgi_opt_add_string_list, &u_dogstatsd_config.metrics_whitelist, 0}, + UWSGI_END_OF_OPTIONS +}; + // configuration of a dogstatsd node struct dogstatsd_node { int fd; @@ -49,6 +64,11 @@ static int dogstatsd_generate_tags(char *metric, size_t metric_len, char *datato if (!token) return -1; + if (u_dogstatsd_config.extra_tags && strlen(u_dogstatsd_config.extra_tags)) { + strncat(datadog_tags, tag_prefix, (MAX_BUFFER_SIZE - strlen(datadog_tags) - strlen(tag_prefix) - 1)); + strncat(datadog_tags, u_dogstatsd_config.extra_tags, (MAX_BUFFER_SIZE - strlen(datadog_tags) - strlen(u_dogstatsd_config.extra_tags) - 1)); + } + while (token != NULL && metric_len >= metric_offset) { metric_offset += strlen(token) + 1; @@ -126,6 +146,10 @@ static int dogstatsd_send_metric(struct uwsgi_buffer *ub, struct uwsgi_stats_pus if (extracted_tags < 0) return -1; + if (u_dogstatsd_config.metrics_whitelist && !uwsgi_string_list_has_item(u_dogstatsd_config.metrics_whitelist, datatog_metric_name, strlen(datatog_metric_name))) { + return 0; + } + if (uwsgi_buffer_append(ub, sn->prefix, sn->prefix_len)) return -1; if (uwsgi_buffer_append(ub, ".", 1)) return -1; @@ -191,12 +215,22 @@ static void stats_pusher_dogstatsd(struct uwsgi_stats_pusher_instance *uspi, tim } // we use the same buffer for all of the packets + if (uwsgi.metrics_cnt <= 0) { + uwsgi_log(" *** WARNING: Dogstatsd stats pusher configured but there are no metrics to push ***\n"); + return; + } + struct uwsgi_buffer *ub = uwsgi_buffer_new(uwsgi.page_size); struct uwsgi_metric *um = uwsgi.metrics; while(um) { + if (u_dogstatsd_config.no_workers && !uwsgi_starts_with(um->name, um->name_len, "worker.", 7)) { + um = um->next; + continue; + } + uwsgi_rlock(uwsgi.metrics_lock); // ignore return value - if (um->type == UWSGI_METRIC_GAUGE) { + if (u_dogstatsd_config.all_gauges || um->type == UWSGI_METRIC_GAUGE) { dogstatsd_send_metric(ub, uspi, um->name, um->name_len, *um->value, "|g"); } else { @@ -214,13 +248,14 @@ static void stats_pusher_dogstatsd(struct uwsgi_stats_pusher_instance *uspi, tim } static void dogstatsd_init(void) { - struct uwsgi_stats_pusher *usp = uwsgi_register_stats_pusher("dogstatsd", stats_pusher_dogstatsd); + struct uwsgi_stats_pusher *usp = uwsgi_register_stats_pusher("dogstatsd", stats_pusher_dogstatsd); // we use a custom format not the JSON one usp->raw = 1; } struct uwsgi_plugin dogstatsd_plugin = { - .name = "dogstatsd", - .on_load = dogstatsd_init, + .name = "dogstatsd", + .options = dogstatsd_options, + .on_load = dogstatsd_init, };