├── .gitignore ├── .travis.yml ├── Changes ├── META.json ├── Makefile ├── README.md ├── Vagrantfile ├── doc └── stats.md ├── sql ├── statsd.sql └── uninstall_statsd.sql ├── src └── statsd.c ├── statsd.control ├── test.sh └── test.sql /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.so 3 | node_modules 4 | .vagrant 5 | sql/statsd--*.sql 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: bash 2 | addons: 3 | postgresql: "9.3" 4 | after_script: 5 | - psql -c 'DROP EXTENSION statsd' -U postgres 6 | - psql -c 'DROP SCHEMA statsd' -U postgres 7 | before_script: 8 | - sudo apt-get install -q -y postgresql-server-dev-all postgresql-common nodejs npm screen 9 | - npm install -q statsd 10 | - screen -dmS statsd node_modules/statsd/bin/statsd node_modules/statsd/exampleConfig.js 11 | - make 12 | - sudo make install 13 | - psql -c 'CREATE EXTENSION statsd' -U postgres 14 | script: 15 | - bash test.sh 16 | -------------------------------------------------------------------------------- /Changes: -------------------------------------------------------------------------------- 1 | Revision history for PostgreSQL extension statsd 2 | 3 | 0.2.0 2014-11-24 4 | - Internal rename of pg_statsd to statsd 5 | - Cleanup of Makefile to work with PostgreSQL 9.0 -> 9.3 6 | - Add explicit tests to check that functions communicate with a local statsd instance 7 | 8 | 0.1.4 2014-11-24 9 | - Failed attempt to fix Makefile 10 | 11 | 0.1.3 2014-07-24 12 | - Minor installation bugfixes 13 | 14 | 0.1.1 2014-04-09 15 | - Bugfix for builtins.h pathing 16 | 17 | 0.1.0 2014-02-25 18 | - Initial release 19 | -------------------------------------------------------------------------------- /META.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "statsd", 3 | "abstract": "statsd client for PostgreSQL", 4 | "description": "Add functions to make statsd calls for gaugues, counters, and timers in PostgreSQL", 5 | "version": "0.2.0", 6 | "maintainer": "Gavin M. Roy ", 7 | "license": [ "bsd" ], 8 | "provides": { 9 | "statsd": { 10 | "abstract": "statsd client for PostgreSQL", 11 | "file": "sql/statsd.sql", 12 | "version": "0.2.0" 13 | } 14 | }, 15 | "prereqs": { 16 | "runtime": { 17 | "requires": { 18 | "PostgreSQL": "9.1.0" 19 | } 20 | } 21 | }, 22 | "generated_by": "Gavin M. Roy", 23 | "resources": { 24 | "bugtracker": { 25 | "web": "https://github.com/AWeber/pg-statsd/issues/" 26 | }, 27 | "repository": { 28 | "url": "git://github.com/AWeber/pg-statsd.git", 29 | "web": "https://github.com/AWeber/pg-statsd/", 30 | "type": "git" 31 | } 32 | }, 33 | "meta-spec": { 34 | "version": "1.0.0", 35 | "url": "http://pgxn.org/meta/spec.txt" 36 | }, 37 | "tags": [ 38 | "statsd" 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | EXTENSION = $(shell grep -m 1 '"name":' META.json | \ 2 | sed -e 's/[[:space:]]*"name":[[:space:]]*"\([^"]*\)",/\1/') 3 | EXTVERSION = $(shell grep -m 1 '[[:space:]]*"version":' META.json | \ 4 | sed -e 's/[[:space:]]*"version":[[:space:]]*"\([^"]*\)",\{0,1\}/\1/') 5 | 6 | DATA = $(filter-out $(wildcard sql/*--*.sql),$(wildcard sql/*.sql)) 7 | DOCS = $(wildcard doc/*.*) 8 | MODULES = $(patsubst %.c,%,$(wildcard src/*.c)) 9 | PG_CONFIG ?= pg_config 10 | PG91 = $(shell $(PG_CONFIG) --version | grep -qE " 8\.| 9\.0" && echo no || echo yes) 11 | 12 | ifeq ($(PG91),yes) 13 | DATA = $(wildcard sql/*--*.sql) 14 | EXTRA_CLEAN = sql/$(EXTENSION)--$(EXTVERSION).sql 15 | endif 16 | 17 | PGXS := $(shell $(PG_CONFIG) --pgxs) 18 | include $(PGXS) 19 | 20 | ifeq ($(PG91),yes) 21 | all: sql/$(EXTENSION)--$(EXTVERSION).sql 22 | 23 | sql/$(EXTENSION)--$(EXTVERSION).sql: sql/$(EXTENSION).sql 24 | cp $< $@ 25 | endif 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | pg-statsd 2 | ========= 3 | pg-statsd is a set of PostgreSQL user-defined functions that provide 4 | and interface to statsd. 5 | 6 | [![Build Status](https://travis-ci.org/aweber/pg-statsd.svg?branch=master)](https://travis-ci.org/aweber/pg-statsd) 7 | 8 | Requirements 9 | ------------ 10 | PostgreSQL 11 | 12 | Installation 13 | ------------ 14 | pg-statsd is available for installation from pgxn: http://pgxn.org. Using the pgxn client: 15 | 16 | ```bash 17 | pgxn install statsd 18 | ``` 19 | 20 | Building 21 | -------- 22 | To build pg-statsd, simply: 23 | 24 | make 25 | make install 26 | 27 | Loading 28 | ------- 29 | 30 | If you're running PostgreSQL 9.1.0 or greater, loading pg-statsd is as simple 31 | as connecting to a database as a super user and running: 32 | 33 | ```sql 34 | CREATE EXTENSION statsd; 35 | ``` 36 | 37 | If you've upgraded your cluster to PostgreSQL 9.1 and already had pg-statsd 38 | installed, you can upgrade it to a properly packaged extension with: 39 | 40 | ```sql 41 | CREATE EXTENSION statsd FROM unpackaged; 42 | ``` 43 | 44 | For versions of PostgreSQL less than 9.1.0, you'll need to run the 45 | installation script: 46 | 47 | ```bash 48 | psql -d mydb -f /path/to/pgsql/share/contrib/statsd.sql 49 | ``` 50 | 51 | Functions 52 | --------- 53 | The following functions are available for use. 54 | 55 | ```sql 56 | # Add a timing value for the specified metric in milliseconds 57 | statsd.add_timing(hostname::text, port::int4, metric::text, value::int4) 58 | 59 | # Increment the specified metric by one 60 | statsd.increment_counter(hostname::text, port::int4, metric::text) 61 | 62 | # Increment the specified metric by a value 63 | statsd.increment_counter(hostname::text, port::int4, metric::text, value::int4) 64 | 65 | # Increment the specified metric by a value and include a sample rate 66 | statsd.increment_counter(hostname::text, port::int4, metric::text, value::int4, sample::float8) 67 | 68 | # Set a gauge value for the specified metric 69 | statsd.set_gauge(hostname::text, port::int4, metric::text, value::float8) 70 | statsd.set_gauge(hostname::text, port::int4, metric::text, value::int4) 71 | ``` 72 | 73 | Examples 74 | -------- 75 | 76 | ```sql 77 | postgres=# SELECT statsd.add_timing('localhost', 8125, 'test-timing1', 70); 78 | add_timing 79 | ------------ 80 | 81 | (1 row) 82 | 83 | postgres=# SELECT statsd.increment_counter('localhost', 8125, 'test-counter-1'); 84 | increment_counter 85 | ------------------- 86 | 87 | (1 row) 88 | 89 | postgres=# SELECT statsd.increment_counter('localhost', 8125, 'test-counter-1', 5); 90 | increment_counter 91 | ------------------- 92 | 93 | (1 row) 94 | 95 | postgres=# SELECT statsd.increment_counter('localhost', 8125, 'test-counter-1', 5, 0.25); 96 | increment_counter 97 | ------------------- 98 | 99 | (1 row) 100 | 101 | postgres=# SELECT statsd.set_gauge('localhost', 8125, 'temperature', 98.7); 102 | set_gauge 103 | ----------- 104 | 105 | (1 row) 106 | ``` 107 | 108 | License 109 | ------- 110 | Copyright (c) 2014, AWeber Communications. 111 | All rights reserved. 112 | 113 | Redistribution and use in source and binary forms, with or without 114 | modification, are permitted provided that the following conditions are 115 | met: 116 | 117 | * Redistributions of source code must retain the above copyright 118 | notice, this list of conditions and the following disclaimer. 119 | * Redistributions in binary form must reproduce the above 120 | copyright notice, this list of conditions and the following 121 | disclaimer in the documentation and/or other materials provided 122 | with the distribution. 123 | * Neither the name pg-statsd nor the names of its contributors may 124 | be used to endorse or promote products derived from this software 125 | without specific prior written 126 | permission. 127 | 128 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 129 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 130 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 131 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 132 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 133 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 134 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 135 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 136 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 137 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 138 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 139 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | # Vagrantfile API/syntax version. Don't touch unless you know what you're doing! 5 | VAGRANTFILE_API_VERSION = "2" 6 | 7 | $install = < /etc/apt/sources.list.d/pgdg.list 11 | apt-get -q update 12 | apt-get -y -q install nodejs npm python-software-properties software-properties-common python-pip make postgresql-${POSTGRES_VERSION} postgresql-client-${POSTGRES_VERSION} postgresql-contrib-${POSTGRES_VERSION} postgresql-server-dev-${POSTGRES_VERSION} pgxnclient 13 | apt-get -q clean 14 | sudo ln -s /usr/bin/nodejs /usr/bin/node 15 | cd /vagrant 16 | npm install -q statsd 17 | screen -dmS statsd /vagrant/node_modules/statsd/bin/statsd /vagrant/node_modules/statsd/exampleConfig.js 18 | INSTALL 19 | 20 | Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| 21 | config.vm.box = "Ubuntu" 22 | config.vm.provision "shell", inline: $install 23 | end 24 | -------------------------------------------------------------------------------- /doc/stats.md: -------------------------------------------------------------------------------- 1 | pg-statsd 2 | ========= 3 | pg-statsd is a set of PostgreSQL user-defined functions that provide 4 | and interface to statsd. 5 | 6 | Requirements 7 | ------------ 8 | PostgreSQL 9 | 10 | Installation 11 | ------------ 12 | pg-statsd is available for installation from pgxn: http://pgxn.org. Using the pgxn client: 13 | 14 | ```bash 15 | pgxn install statsd 16 | ``` 17 | 18 | Building 19 | -------- 20 | To build pg-statsd, simply: 21 | 22 | make 23 | make install 24 | 25 | Loading 26 | ------- 27 | 28 | If you're running PostgreSQL 9.1.0 or greater, loading pg-statsd is as simple 29 | as connecting to a database as a super user and running: 30 | 31 | ```sql 32 | CREATE EXTENSION statsd; 33 | ``` 34 | 35 | If you've upgraded your cluster to PostgreSQL 9.1 and already had pg-statsd 36 | installed, you can upgrade it to a properly packaged extension with: 37 | 38 | ```sql 39 | CREATE EXTENSION statsd FROM unpackaged; 40 | ``` 41 | 42 | For versions of PostgreSQL less than 9.1.0, you'll need to run the 43 | installation script: 44 | 45 | ```bash 46 | psql -d mydb -f /path/to/pgsql/share/contrib/statsd.sql 47 | ``` 48 | 49 | Functions 50 | --------- 51 | The following functions are available for use. 52 | 53 | ```sql 54 | # Add a timing value for the specified metric in milliseconds 55 | statsd.add_timing(hostname::text, port::int4, metric::text, value::int4) 56 | 57 | # Increment the specified metric by one 58 | statsd.increment_counter(hostname::text, port::int4, metric::text) 59 | 60 | # Increment the specified metric by a value 61 | statsd.increment_counter(hostname::text, port::int4, metric::text, value::int4) 62 | 63 | # Increment the specified metric by a value and include a sample rate 64 | statsd.increment_counter(hostname::text, port::int4, metric::text, value::int4, sample::float8) 65 | 66 | # Set a gauge value for the specified metric 67 | statsd.set_gauge(hostname::text, port::int4, metric::text, value::float8) 68 | statsd.set_gauge(hostname::text, port::int4, metric::text, value::int4) 69 | ``` 70 | 71 | Examples 72 | -------- 73 | 74 | ```sql 75 | postgres=# SELECT statsd.add_timing('localhost', 8125, 'test-timing1', 70); 76 | add_timing 77 | ------------ 78 | 79 | (1 row) 80 | 81 | postgres=# SELECT statsd.increment_counter('localhost', 8125, 'test-counter-1'); 82 | increment_counter 83 | ------------------- 84 | 85 | (1 row) 86 | 87 | postgres=# SELECT statsd.increment_counter('localhost', 8125, 'test-counter-1', 5); 88 | increment_counter 89 | ------------------- 90 | 91 | (1 row) 92 | 93 | postgres=# SELECT statsd.increment_counter('localhost', 8125, 'test-counter-1', 5, 0.25); 94 | increment_counter 95 | ------------------- 96 | 97 | (1 row) 98 | 99 | postgres=# SELECT statsd.set_gauge('localhost', 8125, 'temperature', 98.7); 100 | set_gauge 101 | ----------- 102 | 103 | (1 row) 104 | ``` 105 | 106 | License 107 | ------- 108 | Copyright (c) 2014, AWeber Communications. 109 | All rights reserved. 110 | 111 | Redistribution and use in source and binary forms, with or without 112 | modification, are permitted provided that the following conditions are 113 | met: 114 | 115 | * Redistributions of source code must retain the above copyright 116 | notice, this list of conditions and the following disclaimer. 117 | * Redistributions in binary form must reproduce the above 118 | copyright notice, this list of conditions and the following 119 | disclaimer in the documentation and/or other materials provided 120 | with the distribution. 121 | * Neither the name pg-statsd nor the names of its contributors may 122 | be used to endorse or promote products derived from this software 123 | without specific prior written 124 | permission. 125 | 126 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 127 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 128 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 129 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 130 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 131 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 132 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 133 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 134 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 135 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 136 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 137 | -------------------------------------------------------------------------------- /sql/statsd.sql: -------------------------------------------------------------------------------- 1 | CREATE FUNCTION statsd.add_timing(text, int4, text, int4) 2 | returns void as 'statsd', 'statsd_add_timing' language C immutable; 3 | 4 | COMMENT ON FUNCTION statsd.add_timing(text, int4, text, int4) IS 5 | 'Add a timing value in milliseconds to statsd for the given metric name (host, port, metric_name, value)'; 6 | 7 | CREATE FUNCTION statsd.increment_counter(text, int4, text) 8 | returns void as 'statsd', 'statsd_increment_counter' language C immutable; 9 | 10 | COMMENT ON FUNCTION statsd.increment_counter(text, int4, text) IS 11 | 'Increment a counter by one (host, port, metric_name)'; 12 | 13 | CREATE FUNCTION statsd.increment_counter(text, int4, text, int4) 14 | returns void as 'statsd', 'statsd_increment_counter_with_value' language C immutable; 15 | 16 | COMMENT ON FUNCTION statsd.increment_counter(text, int4, text, int4) IS 17 | 'Increment a counter by the specified value (host, port, metric_name, value)'; 18 | 19 | CREATE FUNCTION statsd.increment_counter(text, int4, text, int4, float8) 20 | returns void as 'statsd', 'statsd_increment_sampled_counter' language C immutable; 21 | 22 | COMMENT ON FUNCTION statsd.increment_counter(text, int4, text, int4, float8) IS 23 | 'Increment a counter by the specified value with a sample rate (host, port, metric_name, value, sample_rate)'; 24 | 25 | CREATE FUNCTION statsd.set_gauge(text, int4, text, float8) 26 | returns void as 'statsd', 'statsd_set_gauge_float8' language C immutable; 27 | 28 | COMMENT ON FUNCTION statsd.set_gauge(text, int4, text, float8) IS 29 | 'Sets a gauge value (host, port, metric_name, value)'; 30 | 31 | CREATE FUNCTION statsd.set_gauge(text, int4, text, int4) 32 | returns void as 'statsd', 'statsd_set_gauge_int32' language C immutable; 33 | 34 | COMMENT ON FUNCTION statsd.set_gauge(text, int4, text, int4) IS 35 | 'Sets a gauge value (host, port, metric_name, value)'; 36 | -------------------------------------------------------------------------------- /sql/uninstall_statsd.sql: -------------------------------------------------------------------------------- 1 | DROP SCHEMA statsd CASCADE; 2 | -------------------------------------------------------------------------------- /src/statsd.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, AWeber Communications. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are 7 | * met: 8 | * 9 | * * Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * * Redistributions in binary form must reproduce the above 12 | * copyright notice, this list of conditions and the following 13 | * disclaimer in the documentation and/or other materials provided 14 | * with the distribution. 15 | * * Neither the name pg-statsd nor the names of its contributors may 16 | * be used to endorse or promote products derived from this software 17 | * without specific prior written 18 | * permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | #include "postgres.h" 41 | #include "fmgr.h" 42 | #include "funcapi.h" 43 | #include "utils/builtins.h" 44 | 45 | #ifdef PG_MODULE_MAGIC 46 | PG_MODULE_MAGIC; 47 | #endif 48 | 49 | Datum statsd_add_timing(PG_FUNCTION_ARGS); 50 | Datum statsd_increment_counter(PG_FUNCTION_ARGS); 51 | Datum statsd_increment_sampled_counter(PG_FUNCTION_ARGS); 52 | Datum statsd_increment_counter_with_value(PG_FUNCTION_ARGS); 53 | Datum statsd_set_gauge_float8(PG_FUNCTION_ARGS); 54 | Datum statsd_set_gauge_int32(PG_FUNCTION_ARGS); 55 | 56 | /** 57 | * Create a socket and connect to the statsd server returning the fd 58 | * 59 | * @param FunctionCallInfoData *fcinfo 60 | * @param char *output 61 | * @return int 62 | */ 63 | static int local_send_metric(FunctionCallInfoData *fcinfo, char *output) { 64 | struct addrinfo *addr, *addrs; 65 | int bytes, err, sockfd; 66 | char ipstr[128], servname[10]; 67 | 68 | // Get host and port 69 | char *host = DatumGetCString(DirectFunctionCall1(textout, 70 | PointerGetDatum(PG_GETARG_TEXT_P(0)))); 71 | snprintf(servname, sizeof(servname), "%d", PG_GETARG_INT32(1)); 72 | 73 | // Get possible addresses to send to 74 | if ((err = getaddrinfo(host, servname, 75 | &(struct addrinfo){.ai_family = AF_UNSPEC, 76 | .ai_socktype = SOCK_DGRAM, 77 | .ai_protocol = 0}, 78 | &addrs)) != 0) 79 | { 80 | ereport(WARNING, 81 | (errcode(ERRCODE_CONNECTION_FAILURE), 82 | errmsg("getaddrinfo(%s): %s", host, gai_strerror(err)))); 83 | return -1; 84 | } 85 | 86 | // Iterate through the addresses returned by getaddrinfo 87 | for (addr = addrs; addr != NULL; addr = addr->ai_next) { 88 | 89 | // Create the socket 90 | sockfd = socket(addr->ai_family, addr->ai_socktype, 0); 91 | if (sockfd == -1) { 92 | ereport(WARNING, 93 | (errcode(ERRCODE_CONNECTION_FAILURE), 94 | errmsg("Error: %s", strerror(errno)))); 95 | continue; 96 | } 97 | 98 | // Send the packet without connecting 99 | bytes = sendto(sockfd, output, strlen(output), 0, addr->ai_addr, addr->ai_addrlen); 100 | if (bytes > 0) { 101 | return bytes; 102 | } 103 | 104 | // Get the IP address for debug 105 | switch(addr->ai_family) { 106 | case AF_INET: 107 | inet_ntop(AF_INET, &(((struct sockaddr_in *)addr)->sin_addr), ipstr, sizeof(ipstr)); 108 | break; 109 | 110 | case AF_INET6: 111 | inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)addr)->sin6_addr), ipstr, sizeof(ipstr)); 112 | break; 113 | } 114 | ereport(INFO, 115 | (errcode(ERRCODE_CONNECTION_FAILURE), 116 | errmsg("%s Error: %s", ipstr, strerror(errno)))); 117 | } 118 | 119 | ereport(WARNING, 120 | (errcode(ERRCODE_CONNECTION_FAILURE), 121 | errmsg("Could not connect to %s:%s - %s", host, servname, strerror(errno)))); 122 | return -1; 123 | } 124 | 125 | /** 126 | * Add a timing value in milliseconds to statsd for the given metric 127 | * 128 | * @param text hostname 129 | * @param int4 port 130 | * @param text metric 131 | * @param int value 132 | */ 133 | PG_FUNCTION_INFO_V1(statsd_add_timing); 134 | Datum 135 | statsd_add_timing(PG_FUNCTION_ARGS){ 136 | if (!PG_ARGISNULL(0)) { 137 | // Get the metric name 138 | char *metric = DatumGetCString(DirectFunctionCall1(textout, 139 | PointerGetDatum(PG_GETARG_TEXT_P(2)))); 140 | 141 | // Build the statsd command to send 142 | int size = strlen(metric) + 12; 143 | char *output = (char *) palloc(size); 144 | snprintf(output, size, "%s:%i|ms", metric, PG_GETARG_INT32(3)); 145 | 146 | // Write to statsd and free the output 147 | local_send_metric(fcinfo, output); 148 | pfree(output); 149 | } 150 | PG_RETURN_VOID(); 151 | } 152 | 153 | /** 154 | * Increment a counter by one 155 | * 156 | * @param text hostname 157 | * @param int4 port 158 | * @param text metric 159 | */ 160 | PG_FUNCTION_INFO_V1(statsd_increment_counter); 161 | Datum 162 | statsd_increment_counter(PG_FUNCTION_ARGS){ 163 | if (!PG_ARGISNULL(0)) { 164 | // Get the metric name 165 | char *metric = DatumGetCString(DirectFunctionCall1(textout, 166 | PointerGetDatum(PG_GETARG_TEXT_P(2)))); 167 | 168 | // Build the statsd command to send 169 | int size = strlen(metric) + 5; 170 | char *output = (char *) palloc(size); 171 | snprintf(output, size, "%s:1|c", metric); 172 | 173 | // Write to statsd and free the output 174 | local_send_metric(fcinfo, output); 175 | pfree(output); 176 | } 177 | PG_RETURN_VOID(); 178 | } 179 | 180 | /** 181 | * Increment a counter with a specified value 182 | * 183 | * @param text hostname 184 | * @param int4 port 185 | * @param text metric 186 | * @param int4 value 187 | */ 188 | PG_FUNCTION_INFO_V1(statsd_increment_counter_with_value); 189 | Datum 190 | statsd_increment_counter_with_value(PG_FUNCTION_ARGS){ 191 | if (!PG_ARGISNULL(0)) { 192 | // Get the metric name 193 | char *metric = DatumGetCString(DirectFunctionCall1(textout, 194 | PointerGetDatum(PG_GETARG_TEXT_P(2)))); 195 | 196 | // Build the statsd command to send 197 | int32 size = strlen(metric) + 128; 198 | char *output = (char *) palloc(size); 199 | snprintf(output, size, "%s:%i|c", metric, PG_GETARG_INT32(3)); 200 | 201 | // Write to statsd and free the output 202 | local_send_metric(fcinfo, output); 203 | pfree(output); 204 | } 205 | PG_RETURN_VOID(); 206 | } 207 | 208 | /** 209 | * Increment a sampled counter with a value 210 | * 211 | * @param text hostname 212 | * @param int4 port 213 | * @param text metric 214 | * @param int4 value 215 | * @param float8 sample 216 | */ 217 | PG_FUNCTION_INFO_V1(statsd_increment_sampled_counter); 218 | Datum 219 | statsd_increment_sampled_counter(PG_FUNCTION_ARGS){ 220 | if (!PG_ARGISNULL(0)) { 221 | // Get the metric name 222 | char *metric = DatumGetCString(DirectFunctionCall1(textout, 223 | PointerGetDatum(PG_GETARG_TEXT_P(2)))); 224 | 225 | // Build the statsd command to send 226 | int32 size = strlen(metric) + 256; 227 | char *output = (char *) palloc(size); 228 | snprintf(output, size, "%s:%d|c|@%f", metric, PG_GETARG_INT32(3), PG_GETARG_FLOAT8(4)); 229 | 230 | // Write to statsd and free the output 231 | local_send_metric(fcinfo, output); 232 | pfree(output); 233 | } 234 | PG_RETURN_VOID(); 235 | } 236 | 237 | /** 238 | * Set a float gauge value on a statsd server 239 | * 240 | * @param text hostname 241 | * @param int4 port 242 | * @param text metric 243 | * @param float8 value 244 | */ 245 | PG_FUNCTION_INFO_V1(statsd_set_gauge_float8); 246 | Datum 247 | statsd_set_gauge_float8(PG_FUNCTION_ARGS){ 248 | if (!PG_ARGISNULL(0)) { 249 | // Get the metric name 250 | char *metric = DatumGetCString(DirectFunctionCall1(textout, 251 | PointerGetDatum(PG_GETARG_TEXT_P(2)))); 252 | 253 | // Build the statsd command to send 254 | int32 size = strlen(metric) + 128; 255 | char *output = (char *) palloc(size); 256 | snprintf(output, size, "%s:%f|g", metric, PG_GETARG_FLOAT8(3)); 257 | 258 | // Write to statsd and free the output 259 | local_send_metric(fcinfo, output); 260 | pfree(output); 261 | } 262 | PG_RETURN_VOID(); 263 | } 264 | 265 | /** 266 | * Set an int32 gauge value on a statsd server 267 | * 268 | * @param text hostname 269 | * @param int4 port 270 | * @param text metric 271 | * @param int4 value 272 | */ 273 | PG_FUNCTION_INFO_V1(statsd_set_gauge_int32); 274 | Datum 275 | statsd_set_gauge_int32(PG_FUNCTION_ARGS){ 276 | if (!PG_ARGISNULL(0)) { 277 | // Get the metric name 278 | char *metric = DatumGetCString(DirectFunctionCall1(textout, 279 | PointerGetDatum(PG_GETARG_TEXT_P(2)))); 280 | 281 | // Build the statsd command to send 282 | int size = strlen(metric) + 12; 283 | char *output = (char *) palloc(size); 284 | snprintf(output, size, "%s:%i|g", metric, PG_GETARG_INT32(3)); 285 | 286 | // Write to statsd and free the output 287 | local_send_metric(fcinfo, output); 288 | pfree(output); 289 | } 290 | PG_RETURN_VOID(); 291 | } 292 | -------------------------------------------------------------------------------- /statsd.control: -------------------------------------------------------------------------------- 1 | # statsd extension 2 | comment = 'statsd client for PostgreSQL' 3 | default_version = '0.2.0' 4 | module_pathname = '$libdir/statsd' 5 | relocatable = false 6 | schema = statsd 7 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | assertContains() 4 | { 5 | echo -n "$1" 6 | if [[ "$3" == *$2* ]]; then 7 | echo "Pass" 8 | else 9 | echo "Fail" 10 | echo 11 | echo " Expected: \"${2}\"" 12 | echo " Received: \"${3}\"" 13 | echo 14 | return 1 15 | fi 16 | return 0 17 | } 18 | 19 | assertEqual() 20 | { 21 | echo -n "$1" 22 | if [ "$2" == "$3" ]; then 23 | echo "Pass" 24 | else 25 | echo "Fail" 26 | echo 27 | echo " Expected: \"${2}\"" 28 | echo " Received: \"${3}\"" 29 | echo 30 | return 1 31 | fi 32 | return 0 33 | } 34 | 35 | if [ "$TRAVIS" == "true" ]; then 36 | echo "Testing pg-statsd" 37 | psql -U postgres -f test.sql 38 | else 39 | su postgres -c "psql -c 'CREATE EXTENSION statsd'" 40 | echo "Testing pg-statsd" 41 | su postgres -c "psql -q -o /dev/null -f test.sql" 42 | su postgres -c "psql -c 'DROP EXTENSION statsd'" 43 | su postgres -c "psql -c 'DROP SCHEMA statsd'" 44 | fi 45 | echo "Getting results" 46 | 47 | RESULT=`echo counters | nc localhost 8126 | grep -v 'END'` 48 | assertContains "Counters: " "'test-counter-1': 7" "$RESULT" 49 | CONTAINERS=$? 50 | 51 | RESULT=`echo gauges | nc localhost 8126 | grep -v 'END'` 52 | assertContains "Gauges: " "'test-gauge': 98" "$RESULT" 53 | GAUGES=$? 54 | 55 | RESULT=`echo timers | nc localhost 8126 | grep -v 'END'` 56 | assertEqual "Timers: " "{ 'test-timing1': [ 70, 50 ] }" "$RESULT" 57 | TIMERS=$? 58 | 59 | echo "Clearing statsd" 60 | echo "delcounters" | nc localhost 8126 | grep -G "/[END]|\n/" 61 | echo "delgauges" | nc localhost 8126 | grep -G "/[END]|\n/" 62 | echo "deltimers" | nc localhost 8126 | grep -G "/[END]|\n/" 63 | 64 | exit $(($CONTAINERS+$GAUGES+$TIMERS)) 65 | -------------------------------------------------------------------------------- /test.sql: -------------------------------------------------------------------------------- 1 | SELECT statsd.add_timing('127.0.0.1', 8125, 'test-timing1', 70); 2 | SELECT statsd.add_timing('127.0.0.1', 8125, 'test-timing1', 50); 3 | SELECT statsd.increment_counter('127.0.0.1', 8125, 'test-counter-1'); 4 | SELECT statsd.increment_counter('127.0.0.1', 8125, 'test-counter-1'); 5 | SELECT statsd.increment_counter('127.0.0.1', 8125, 'test-counter-1'); 6 | SELECT statsd.increment_counter('127.0.0.1', 8125, 'test-counter-1'); 7 | SELECT statsd.increment_counter('127.0.0.1', 8125, 'test-counter-1'); 8 | SELECT statsd.increment_counter('127.0.0.1', 8125, 'test-counter-1'); 9 | SELECT statsd.increment_counter('127.0.0.1', 8125, 'test-counter-1'); 10 | SELECT statsd.set_gauge('127.0.0.1', 8125, 'test-gauge', 98); 11 | --------------------------------------------------------------------------------