├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── example ├── app.ini ├── app.py └── run.sh ├── plugin.c ├── test.sh ├── tests └── test.py └── uwsgiplugin.py /.gitignore: -------------------------------------------------------------------------------- 1 | dogstatsd_plugin.so 2 | .uwsgi_plugins_builder/ 3 | example/uwsgi-dogstatsd/ 4 | tests/uwsgi-dogstatsd/ 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | dist: trusty 3 | python: 4 | - "2.7" 5 | - "2.6" 6 | 7 | script: bash -c ./test.sh 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Simplified BSD License 2 | 3 | Copyright (c) 2009, Boxed Ice 4 | Copyright (c) 2010-2014, Datadog 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, 11 | this list of conditions and the following disclaimer. 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | * Neither the name of Boxed Ice nor the names of its contributors 16 | may be used to endorse or promote products derived from this software 17 | without specific prior written permission. 18 | * Neither the name of Datadog nor the names of its contributors 19 | may be used to endorse or promote products derived from this software 20 | without specific prior written permission. 21 | 22 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 23 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 25 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 26 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 27 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 28 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 29 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 30 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 31 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/DataDog/uwsgi-dogstatsd.svg?branch=master)](https://travis-ci.org/DataDog/uwsgi-dogstatsd) 2 | 3 | uwsgi-dogstatsd 4 | =============== 5 | 6 | [uWSGI] plugin to emit [DogStatsD] metrics to [Datadog] via the [Datadog Agent] 7 | 8 | 9 | INSTALL 10 | ======= 11 | 12 | The plugin is 2.x friendly, so installation can be done directly from the repo: 13 | 14 | ```sh 15 | uwsgi --build-plugin https://github.com/Datadog/uwsgi-dogstatsd 16 | ``` 17 | 18 | If you are packaging the plugin for distribution, please read the [uWSGI Guide for Packagers](http://projects.unbit.it/uwsgi/wiki/Guide4Packagers) 19 | on plugin placement and extra directives like `plugin_dir`. 20 | 21 | USAGE 22 | ===== 23 | 24 | Depends on a Datadog Agent to be installed and by default listens for dogstatsd on port 8125. 25 | 26 | Configure your `.ini` file to enable the metrics subsystem, and enable the dogstatsd plugin. 27 | 28 | Here's a 29 | 30 | ```ini 31 | [uwsgi] 32 | master = true 33 | processes = 8 34 | threads = 4 35 | 36 | http = :9090 37 | 38 | # DogStatsD plugin configuration 39 | enable-metrics = true 40 | plugin = dogstatsd 41 | stats-push = dogstatsd:127.0.0.1:8125,myapp 42 | 43 | # Application to load 44 | wsgi-file = app.py 45 | ... 46 | ``` 47 | 48 | You can also add additional tags or filter which metrics are published (or how they are published) using one or more optional configuration options: 49 | 50 | ```ini 51 | stats-push = dogstatsd:127.0.0.1:8125,myapp 52 | dogstatsd-extra-tags = app:foo_service,instance:1 53 | dogstatsd-no-workers = true 54 | dogstatsd-all-gauges = true 55 | dogstatsd-whitelist-metric = core.busy_workers 56 | dogstatsd-whitelist-metric = core.idle_workers 57 | dogstatsd-whitelist-metric = core.overloaded 58 | dogstatsd-whitelist-metric = socket.listen_queue 59 | ``` 60 | 61 | This will begin producing metrics with the prefix defined in the configuration, `myapp` here: 62 | 63 | ```console 64 | myapp.core.avg_response_time 65 | myapp.core.busy_workers 66 | myapp.core.idle_workers 67 | myapp.core.overloaded 68 | myapp.core.routed_signals 69 | myapp.core.total_rss 70 | myapp.core.total_tx 71 | myapp.core.total_vsz 72 | myapp.core.unrouted_signals 73 | myapp.rss_size 74 | myapp.socket.listen_queue 75 | myapp.vsz_size 76 | myapp.worker.avg_response_time 77 | myapp.worker.core.exceptions 78 | myapp.worker.core.offloaded_requests 79 | myapp.worker.core.read_errors 80 | myapp.worker.core.requests 81 | myapp.worker.core.routed_requests 82 | myapp.worker.core.static_requests 83 | myapp.worker.core.write_errors 84 | myapp.worker.delta_requests 85 | myapp.worker.failed_requests 86 | myapp.worker.requests 87 | myapp.worker.respawns 88 | myapp.worker.rss_size 89 | myapp.worker.total_tx 90 | myapp.worker.vsz_size 91 | ``` 92 | 93 | The metrics are tagged and split where there are more than one occurrence, such as CPU core, worker. 94 | 95 | Read more on the [uWSGI Metrics subsystem] for further explanation on metrics provided. 96 | 97 | [Datadog]: http://www.datadog.com/ 98 | [Datadog Agent]: https://github.com/DataDog/dd-agent 99 | [DogStatsD]: http://docs.datadoghq.com/guides/dogstatsd/ 100 | [uWSGI]: http://uwsgi-docs.readthedocs.org/ 101 | [uWSGI Metrics subsystem]: http://uwsgi-docs.readthedocs.org/en/latest/Metrics.html 102 | -------------------------------------------------------------------------------- /example/app.ini: -------------------------------------------------------------------------------- 1 | [uwsgi] 2 | master = true 3 | processes = 1 4 | threads = 1 5 | 6 | http = :9090 7 | 8 | # DogStatsD plugin configuration 9 | enable-metrics = true 10 | plugin = dogstatsd 11 | stats-push = dogstatsd:127.0.0.1:8125,myapp 12 | 13 | # Application to load 14 | wsgi-file = app.py 15 | 16 | -------------------------------------------------------------------------------- /example/app.py: -------------------------------------------------------------------------------- 1 | def application(env, start_response): 2 | start_response('200 OK', [('Content-Type', 'text/html')]) 3 | return ["Hello World"] 4 | -------------------------------------------------------------------------------- /example/run.sh: -------------------------------------------------------------------------------- 1 | pip install uWSGI==2.0.17 2 | 3 | uwsgi --build-plugin .. 4 | 5 | uwsgi app.ini 6 | 7 | -------------------------------------------------------------------------------- /plugin.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define MAX_BUFFER_SIZE 8192 4 | 5 | /* 6 | 7 | this is a stats pusher plugin for DogStatsD: 8 | 9 | --stats-push dogstatsd:address[,prefix] 10 | 11 | example: 12 | 13 | --stats-push dogstatsd:127.0.0.1:8125,myinstance 14 | 15 | exports values exposed by the metric subsystem to a Datadog Agent StatsD server 16 | 17 | */ 18 | 19 | extern struct uwsgi_server uwsgi; 20 | 21 | struct dogstatsd_config { 22 | int no_workers; 23 | int all_gauges; 24 | char *extra_tags; 25 | struct uwsgi_string_list *metrics_whitelist; 26 | } u_dogstatsd_config; 27 | 28 | static struct uwsgi_option dogstatsd_options[] = { 29 | {"dogstatsd-no-workers", no_argument, 0, "disable generation of single-worker metrics", uwsgi_opt_true, &u_dogstatsd_config.no_workers, 0}, 30 | {"dogstatsd-all-gauges", no_argument, 0, "push all metrics to dogstatsd as gauges", uwsgi_opt_true, &u_dogstatsd_config.all_gauges, 0}, 31 | {"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}, 32 | {"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}, 33 | UWSGI_END_OF_OPTIONS 34 | }; 35 | 36 | // configuration of a dogstatsd node 37 | struct dogstatsd_node { 38 | int fd; 39 | union uwsgi_sockaddr addr; 40 | socklen_t addr_len; 41 | char *prefix; 42 | uint16_t prefix_len; 43 | }; 44 | 45 | static int dogstatsd_generate_tags(char *metric, size_t metric_len, char *datatog_metric_name, char *datadog_tags) { 46 | char *start = metric; 47 | size_t metric_offset = 0; 48 | 49 | static char metric_separator[] = "."; 50 | static char tag_separator[] = ","; 51 | static char tag_colon = ':'; 52 | static char tag_prefix[] = "|#"; 53 | 54 | long string_to_int; 55 | char *token = NULL; 56 | char *ctxt = NULL; 57 | char *key = NULL; 58 | char *next_character = NULL; 59 | 60 | errno = 0; 61 | 62 | token = strtok_r(start, metric_separator, &ctxt); 63 | 64 | if (!token) 65 | return -1; 66 | 67 | if (u_dogstatsd_config.extra_tags && strlen(u_dogstatsd_config.extra_tags)) { 68 | strncat(datadog_tags, tag_prefix, (MAX_BUFFER_SIZE - strlen(datadog_tags) - strlen(tag_prefix) - 1)); 69 | strncat(datadog_tags, u_dogstatsd_config.extra_tags, (MAX_BUFFER_SIZE - strlen(datadog_tags) - strlen(u_dogstatsd_config.extra_tags) - 1)); 70 | } 71 | 72 | while (token != NULL && metric_len >= metric_offset) { 73 | 74 | metric_offset += strlen(token) + 1; 75 | start = metric + metric_offset; 76 | 77 | // try to convert token into integer 78 | string_to_int = strtol(token, &next_character, 10); 79 | 80 | // stop processing if string_to_int is out of range 81 | if ((string_to_int == LONG_MIN || string_to_int == LONG_MAX) && errno == ERANGE) 82 | return -1; 83 | 84 | // if we've got a number and a tag value: 85 | if (next_character != token && key) { 86 | 87 | // start with tag_separator if we already have some tags 88 | // otherwise put the tag_prefix 89 | if (strlen(datadog_tags)) 90 | strncat(datadog_tags, tag_separator, (MAX_BUFFER_SIZE - strlen(datadog_tags) - strlen(tag_separator) - 1)); 91 | else 92 | strncat(datadog_tags, tag_prefix, (MAX_BUFFER_SIZE - strlen(datadog_tags) - strlen(tag_prefix) - 1)); 93 | 94 | // append new tag 95 | strncat(datadog_tags, key, (MAX_BUFFER_SIZE - strlen(datadog_tags) - strlen(key) - 1)); 96 | strncat(datadog_tags, &tag_colon, 1); 97 | strncat(datadog_tags, token, (MAX_BUFFER_SIZE - strlen(datadog_tags) - strlen(token) - 1)); 98 | 99 | } else { 100 | 101 | // store token as a key for the next iteration 102 | key = token; 103 | 104 | // start with metric_separator if we already have some metrics 105 | if (strlen(datatog_metric_name)) 106 | strncat(datatog_metric_name, metric_separator, (MAX_BUFFER_SIZE - strlen(datatog_metric_name) - strlen(metric_separator) - 1)); 107 | 108 | // add token 109 | strncat(datatog_metric_name, token, (MAX_BUFFER_SIZE - strlen(datatog_metric_name) - strlen(token) - 1)); 110 | } 111 | 112 | // try to generate tokens before we iterate again 113 | token = strtok_r(NULL, metric_separator, &ctxt); 114 | } 115 | 116 | return strlen(datatog_metric_name); 117 | } 118 | 119 | 120 | static int dogstatsd_send_metric(struct uwsgi_buffer *ub, struct uwsgi_stats_pusher_instance *uspi, char *metric, size_t metric_len, int64_t value, char type[2]) { 121 | struct dogstatsd_node *sn = (struct dogstatsd_node *) uspi->data; 122 | 123 | char datatog_metric_name[MAX_BUFFER_SIZE]; 124 | char datadog_tags[MAX_BUFFER_SIZE]; 125 | char raw_metric_name[MAX_BUFFER_SIZE]; 126 | 127 | int extracted_tags = 0; 128 | 129 | // check if we can handle such a metric length 130 | if (metric_len >= MAX_BUFFER_SIZE) 131 | return -1; 132 | 133 | // reset the buffer 134 | ub->pos = 0; 135 | 136 | // sanitize buffers 137 | memset(datadog_tags, 0, MAX_BUFFER_SIZE); 138 | memset(datatog_metric_name, 0, MAX_BUFFER_SIZE); 139 | 140 | // let's copy original metric name before we start 141 | strncpy(raw_metric_name, metric, metric_len + 1); 142 | 143 | // try to extract tags 144 | extracted_tags = dogstatsd_generate_tags(raw_metric_name, metric_len, datatog_metric_name, datadog_tags); 145 | 146 | if (extracted_tags < 0) 147 | return -1; 148 | 149 | if (u_dogstatsd_config.metrics_whitelist && !uwsgi_string_list_has_item(u_dogstatsd_config.metrics_whitelist, datatog_metric_name, strlen(datatog_metric_name))) { 150 | return 0; 151 | } 152 | 153 | if (uwsgi_buffer_append(ub, sn->prefix, sn->prefix_len)) return -1; 154 | if (uwsgi_buffer_append(ub, ".", 1)) return -1; 155 | 156 | // put the datatog_metric_name if we found some tags 157 | if (extracted_tags) { 158 | if (uwsgi_buffer_append(ub, datatog_metric_name, strlen(datatog_metric_name))) return -1; 159 | } else { 160 | if (uwsgi_buffer_append(ub, metric, strlen(metric))) return -1; 161 | } 162 | 163 | if (uwsgi_buffer_append(ub, ":", 1)) return -1; 164 | if (uwsgi_buffer_num64(ub, value)) return -1; 165 | if (uwsgi_buffer_append(ub, type, 2)) return -1; 166 | 167 | // add tags metadata if there are any 168 | if (extracted_tags) { 169 | if (uwsgi_buffer_append(ub, datadog_tags, strlen(datadog_tags))) return -1; 170 | } 171 | 172 | if (sendto(sn->fd, ub->buf, ub->pos, 0, (struct sockaddr *) &sn->addr.sa_in, sn->addr_len) < 0) { 173 | uwsgi_error("dogstatsd_send_metric()/sendto()"); 174 | } 175 | 176 | return 0; 177 | } 178 | 179 | 180 | static void stats_pusher_dogstatsd(struct uwsgi_stats_pusher_instance *uspi, time_t now, char *json, size_t json_len) { 181 | 182 | if (!uspi->configured) { 183 | struct dogstatsd_node *sn = uwsgi_calloc(sizeof(struct dogstatsd_node)); 184 | char *comma = strchr(uspi->arg, ','); 185 | if (comma) { 186 | sn->prefix = comma+1; 187 | sn->prefix_len = strlen(sn->prefix); 188 | *comma = 0; 189 | } 190 | else { 191 | sn->prefix = "uwsgi"; 192 | sn->prefix_len = 5; 193 | } 194 | 195 | char *colon = strchr(uspi->arg, ':'); 196 | if (!colon) { 197 | uwsgi_log("invalid dd address %s\n", uspi->arg); 198 | if (comma) *comma = ','; 199 | free(sn); 200 | return; 201 | } 202 | sn->addr_len = socket_to_in_addr(uspi->arg, colon, 0, &sn->addr.sa_in); 203 | 204 | sn->fd = socket(AF_INET, SOCK_DGRAM, 0); 205 | if (sn->fd < 0) { 206 | uwsgi_error("stats_pusher_dogstatsd()/socket()"); 207 | if (comma) *comma = ','; 208 | free(sn); 209 | return; 210 | } 211 | uwsgi_socket_nb(sn->fd); 212 | if (comma) *comma = ','; 213 | uspi->data = sn; 214 | uspi->configured = 1; 215 | } 216 | 217 | // we use the same buffer for all of the packets 218 | if (uwsgi.metrics_cnt <= 0) { 219 | uwsgi_log(" *** WARNING: Dogstatsd stats pusher configured but there are no metrics to push ***\n"); 220 | return; 221 | } 222 | 223 | struct uwsgi_buffer *ub = uwsgi_buffer_new(uwsgi.page_size); 224 | struct uwsgi_metric *um = uwsgi.metrics; 225 | while(um) { 226 | if (u_dogstatsd_config.no_workers && !uwsgi_starts_with(um->name, um->name_len, "worker.", 7)) { 227 | um = um->next; 228 | continue; 229 | } 230 | 231 | uwsgi_rlock(uwsgi.metrics_lock); 232 | // ignore return value 233 | if (u_dogstatsd_config.all_gauges || um->type == UWSGI_METRIC_GAUGE) { 234 | dogstatsd_send_metric(ub, uspi, um->name, um->name_len, *um->value, "|g"); 235 | } 236 | else { 237 | dogstatsd_send_metric(ub, uspi, um->name, um->name_len, *um->value, "|c"); 238 | } 239 | uwsgi_rwunlock(uwsgi.metrics_lock); 240 | if (um->reset_after_push){ 241 | uwsgi_wlock(uwsgi.metrics_lock); 242 | *um->value = um->initial_value; 243 | uwsgi_rwunlock(uwsgi.metrics_lock); 244 | } 245 | um = um->next; 246 | } 247 | uwsgi_buffer_destroy(ub); 248 | } 249 | 250 | static void dogstatsd_init(void) { 251 | struct uwsgi_stats_pusher *usp = uwsgi_register_stats_pusher("dogstatsd", stats_pusher_dogstatsd); 252 | // we use a custom format not the JSON one 253 | usp->raw = 1; 254 | } 255 | 256 | struct uwsgi_plugin dogstatsd_plugin = { 257 | 258 | .name = "dogstatsd", 259 | .options = dogstatsd_options, 260 | .on_load = dogstatsd_init, 261 | }; 262 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd example && ./run.sh & 4 | 5 | until $(curl --output /dev/null -k --silent --fail http://localhost:9090/); do 6 | echo 'Waiting for uwsgi...' 7 | sleep 1 8 | done 9 | 10 | python tests/test.py 11 | exit $? 12 | -------------------------------------------------------------------------------- /tests/test.py: -------------------------------------------------------------------------------- 1 | import select 2 | import socket 3 | import sys 4 | import threading 5 | import urllib2 6 | import copy 7 | from time import time, sleep 8 | 9 | UDP_SOCKET_TIMEOUT = 5 10 | exitFlag = 0 11 | exitValue = 0 12 | 13 | def setExitFlag(n): 14 | global exitFlag 15 | exitFlag = n 16 | 17 | class Data(object): 18 | """ 19 | The data aggregated from the uwsgi app 20 | """ 21 | def __init__(self): 22 | self.data = {} 23 | self.changed = {} 24 | self.isChanged = False 25 | self.dataLock = threading.Lock() 26 | 27 | def parse_packet(self, packet): 28 | tags = None 29 | metadata = packet.split('|') 30 | if (len(metadata) < 2): 31 | raise Exception('Unparseable metric packet: %s' % packet) 32 | 33 | name_value = metadata[0].split(':') 34 | metric_type = metadata[1] 35 | if (len(metadata) == 3): 36 | tags = metadata[2].split(',') 37 | if (len(tags) < 1 or not tags[0].startswith('#')): 38 | raise Exception('Unparseable metric packet: %s' % packet) 39 | tags[0] = tags[0][1:] 40 | 41 | if (len(name_value) != 2): 42 | raise Exception('Unparseable metric packet: %s' % packet) 43 | 44 | 45 | metric = { 46 | 'name': name_value[0], 47 | 'value': name_value[1], 48 | 'metric_type': metric_type, 49 | 'tags': tags 50 | } 51 | self.dataLock.acquire() 52 | if name_value[0] in self.data and self.data[name_value[0]]['value'] != name_value[1]: 53 | self.changed[name_value[0]] = int(name_value[1]) 54 | if name_value[0] == "myapp.worker.requests": 55 | self.isChanged = True 56 | self.data[name_value[0]] = metric 57 | self.dataLock.release() 58 | 59 | def new_packets(self, packets): 60 | packets = unicode(packets, 'utf-8', errors='replace') 61 | for packet in packets.splitlines(): 62 | if not packet.strip(): 63 | continue 64 | self.parse_packet(packet) 65 | 66 | def getChangedAttributes(self): 67 | self.dataLock.acquire() 68 | changedCopy = copy.deepcopy(self.changed) 69 | self.dataLock.release() 70 | return changedCopy 71 | 72 | def get_data(self): 73 | return self.data 74 | 75 | def ready(self): 76 | return self.isChanged 77 | 78 | def reset(self): 79 | self.isChanged = False 80 | 81 | class Test(threading.Thread): 82 | """ 83 | The class which trigger tests and check results 84 | """ 85 | 86 | COUNT_METRICS = [ 87 | 'myapp.worker.requests', 88 | 'myapp.worker.delta_requests', 89 | 'myapp.worker.core.requests' 90 | ] 91 | 92 | INC_METRICS = [ 93 | 'myapp.worker.total_tx', 94 | 'myapp.worker.respawns' 95 | ] 96 | 97 | VAL_METRICS = { 98 | 'myapp.worker.avg_response_time': (50, 250) 99 | } 100 | 101 | def __init__(self, data): 102 | threading.Thread.__init__(self) 103 | self.data = data 104 | self.tests = 4 105 | self.success = 0 106 | self.failure = 0 107 | self.errors = [] 108 | 109 | def setExitValue(self, val): 110 | global exitValue 111 | exitValue = val 112 | 113 | def check(self): 114 | attributes_changed = self.data.getChangedAttributes() 115 | 116 | if hasattr(self, 'oldData') and self.tests < 4: 117 | for k in self.COUNT_METRICS: 118 | if k in attributes_changed and k in self.oldData and attributes_changed[k] == self.oldData[k] + 1: 119 | self.success += 1 120 | else: 121 | self.setExitValue(1) 122 | self.failure += 1 123 | self.errors.append(k) 124 | 125 | for k in self.INC_METRICS: 126 | if k in attributes_changed and k in self.oldData and attributes_changed[k] >= self.oldData[k]: 127 | self.success += 1 128 | else: 129 | self.setExitValue(1) 130 | self.failure += 1 131 | self.errors.append(k) 132 | 133 | for k, v in self.VAL_METRICS.iteritems(): 134 | if k in attributes_changed and k in self.oldData and v[0] <= attributes_changed[k] <= v[1]: 135 | self.success += 1 136 | else: 137 | self.setExitValue(1) 138 | self.failure += 1 139 | self.errors.append(k) 140 | 141 | self.oldData = attributes_changed 142 | 143 | def printResult(self): 144 | print "################################################################################" 145 | print "RESULTS" 146 | print "################################################################################" 147 | print "" 148 | print "SUCCESS: %d/%d" % (self.success, self.success + self.failure) 149 | print "" 150 | print "FAILURE: %d/%d" % (self.failure, self.success + self.failure) 151 | print "" 152 | print "################################################################################" 153 | print "" 154 | if self.failure > 0: 155 | print "Metrics failed:" 156 | for m in self.errors: 157 | print "* %s" % m 158 | print "" 159 | print "################################################################################" 160 | 161 | def run(self): 162 | print "TEST IN PROGRESS" 163 | sleep(10) 164 | self.check() 165 | while self.tests > 0: 166 | ready = 0 167 | timeout = 30 168 | test = urllib2.urlopen("http://localhost:9090").read() 169 | if test == "Hello World": 170 | while not ready and timeout > 0: 171 | if self.data.ready(): 172 | ready = 1 173 | timeout -= 1 174 | sleep(1) 175 | 176 | if ready: 177 | self.check() 178 | self.data.reset() 179 | else: 180 | print "Test failed: cannot aggregate metrics change" 181 | else: 182 | print "Error while testing, please check if the web application is running" 183 | self.tests -= 1 184 | self.printResult() 185 | setExitFlag(1) 186 | 187 | class Server(threading.Thread): 188 | """ 189 | The process which will listen on the statd port 190 | """ 191 | config = { 192 | 'host': 'localhost', 193 | 'port': 8125 194 | } 195 | 196 | def __init__(self, data): 197 | threading.Thread.__init__(self) 198 | self.data = data 199 | self.buffer_size = 1024 * 8 200 | self.address = (self.config['host'], self.config['port']) 201 | 202 | def run(self): 203 | self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 204 | self.socket.setblocking(0) 205 | try: 206 | self.socket.bind(self.address) 207 | except socket.gaierror: 208 | if self.address[0] == 'localhost': 209 | log.warning("Warning localhost seems undefined in your host file, using 127.0.0.1 instead") 210 | self.address = ('127.0.0.1', self.address[1]) 211 | self.socket.bind(self.address) 212 | 213 | print "Listening on host & port: %s" % str(self.address) 214 | 215 | sock = [self.socket] 216 | select_select = select.select 217 | timeout = UDP_SOCKET_TIMEOUT 218 | 219 | while not exitFlag: 220 | try: 221 | ready = select_select(sock, [], [], timeout) 222 | if ready[0]: 223 | message = self.socket.recv(self.buffer_size) 224 | self.data.new_packets(message) 225 | except Exception: 226 | print 'Error receiving datagram' 227 | 228 | def main(): 229 | data = Data() 230 | server = Server(data) 231 | test = Test(data) 232 | server.start() 233 | test.start() 234 | while not exitFlag: 235 | pass 236 | server.join() 237 | test.join() 238 | print 'END TEST: Exiting' 239 | return exitValue 240 | 241 | if __name__ == '__main__': 242 | sys.exit(main()) 243 | -------------------------------------------------------------------------------- /uwsgiplugin.py: -------------------------------------------------------------------------------- 1 | NAME = 'dogstatsd' 2 | 3 | CFLAGS = [] 4 | LDFLAGS = [] 5 | LIBS = [] 6 | 7 | GCC_LIST = ['plugin'] 8 | --------------------------------------------------------------------------------