├── .gitignore ├── COPYING ├── LICENSE ├── Makefile.am ├── README.rst ├── autogen.sh ├── configure.ac ├── m4 └── PLACEHOLDER └── src ├── Makefile.am ├── tests └── test01.vtc ├── vmod_statsd.c └── vmod_statsd.vcc /.gitignore: -------------------------------------------------------------------------------- 1 | Makefile 2 | Makefile.in 3 | .deps/ 4 | .libs/ 5 | *.o 6 | *.lo 7 | *.la 8 | *~ 9 | 10 | /aclocal.m4 11 | /autom4te.cache/ 12 | /compile 13 | /config.guess 14 | /config.h 15 | /config.h.in 16 | /config.log 17 | /config.status 18 | /config.sub 19 | /configure 20 | /depcomp 21 | /install-sh 22 | /libtool 23 | /ltmain.sh 24 | /missing 25 | /stamp-h1 26 | /m4/ 27 | 28 | /src/vcc_if.c 29 | /src/vcc_if.h 30 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Varnish Software AS 2 | ... 3 | See LICENSE for details. 4 | 5 | You're free to use and distribute this under terms in the 6 | LICENSE. Please add your relevant copyright statements. 7 | 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Jos Boumans & Krux Digital 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | ACLOCAL_AMFLAGS = -I m4 2 | 3 | SUBDIRS = src 4 | 5 | EXTRA_DIST = README.rst 6 | 7 | dist_man_MANS = vmod_statsd.3 8 | MAINTAINERCLEANFILES = $(dist_man_MANS) 9 | 10 | vmod_statsd.3: README.rst 11 | if HAVE_RST2MAN 12 | ${RST2MAN} README.rst $@ 13 | else 14 | @echo "========================================" 15 | @echo "You need rst2man installed to make dist" 16 | @echo "========================================" 17 | @false 18 | endif 19 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | vmod_statsd 3 | ============ 4 | 5 | ---------------------- 6 | Varnish Statsd Module 7 | ---------------------- 8 | 9 | :Author: Jos Boumans 10 | :Date: 2014-01-14 11 | :Version: 1.2 12 | :Manual section: 3 13 | 14 | SYNOPSIS 15 | ======== 16 | 17 | import statsd; 18 | 19 | sub vcl_init { 20 | # Optional, defaults to localhost:8125 21 | statsd.server( "statsd.example.com", "8125" ); 22 | } 23 | 24 | sub vcl_deliver { 25 | statsd.incr( "incr" ); 26 | statsd.gauge( "gauge", 42 ); 27 | statsd.timing( "timing", 42 ); 28 | statsd.counter( "counter", 42 ); 29 | } 30 | 31 | DESCRIPTION 32 | =========== 33 | 34 | Varnish Module (vmod) for sending statistics to Statsd. 35 | 36 | See https://github.com/etsy/statsd for documentation on Statsd. 37 | 38 | FUNCTIONS 39 | ========= 40 | 41 | server 42 | ------ 43 | 44 | Prototype:: 45 | 46 | server(STRING S, STRING S) 47 | 48 | Return value 49 | NONE 50 | Description 51 | Set the address of your Statsd server. 52 | Best used in vcl_init. Defaults to "localhost", "8125" 53 | 54 | Example:: 55 | 56 | statsd.server( "statsd.example.com", "8125" ); 57 | 58 | prefix 59 | ------ 60 | 61 | Prototype:: 62 | 63 | prefix(STRING S) 64 | 65 | Return value 66 | NONE 67 | Description 68 | Set a string to prefix to all the stats you will be sending. 69 | Best used in vcl_init. Defaults to an empty string. 70 | 71 | Example:: 72 | 73 | # statsd.incr( "foo" ) will now send "dev.foo" to Statsd 74 | statsd.prefix( "dev." ); 75 | 76 | suffix 77 | ------ 78 | 79 | Prototype:: 80 | 81 | suffix(STRING S) 82 | 83 | Return value 84 | NONE 85 | Description 86 | Set a string to suffix to all the stats you will be sending. 87 | Best used in vcl_init. Defaults to an empty string. 88 | 89 | Example:: 90 | 91 | # statsd.incr( "foo" ) will now send "foo.dev" to Statsd 92 | statsd.suffix( ".dev" ); 93 | 94 | incr 95 | ---- 96 | 97 | Prototype:: 98 | 99 | incr(STRING S) 100 | 101 | Return value 102 | NONE 103 | Description 104 | Send a stat counter with value '1' to Statsd. Will be prefixed & suffixed 105 | with whatever you set statsd.prefix & statsd.suffix to. 106 | 107 | Example:: 108 | 109 | statsd.incr( "foo" ); 110 | 111 | counter 112 | ------- 113 | 114 | Prototype:: 115 | 116 | counter(STRING S, INT I) 117 | 118 | Return value 119 | NONE 120 | Description 121 | Send a stat counter with value I to Statsd. Will be prefixed & suffixed 122 | with whatever you set statsd.prefix & statsd.suffix to. 123 | 124 | Example:: 125 | 126 | statsd.counter( "foo", 42 ); 127 | 128 | timing 129 | ------- 130 | 131 | Prototype:: 132 | 133 | timing(STRING S, INT I) 134 | 135 | Return value 136 | NONE 137 | Description 138 | Send a stat timer with value I to Statsd. Will be prefixed & suffixed 139 | with whatever you set statsd.prefix & statsd.suffix to. 140 | 141 | Example:: 142 | 143 | statsd.timing( "foo", 42 ); 144 | 145 | gauge 146 | ----- 147 | 148 | Prototype:: 149 | 150 | gauge(STRING S, INT I) 151 | 152 | Return value 153 | NONE 154 | Description 155 | Send a stat gauge with value I to Statsd. Will be prefixed & suffixed 156 | with whatever you set statsd.prefix & statsd.suffix to. 157 | 158 | Example:: 159 | 160 | statsd.gauge( "foo", 42 ); 161 | 162 | 163 | INSTALLATION 164 | ============ 165 | 166 | To install this module, you'll need to install some prerequisites. On Ubuntu, 167 | you can get these by running:: 168 | 169 | $ apt-get install automake libtool python-docutils libvarnishapi-dev make 170 | 171 | You will also need a compiled version of the varnish source code, which you 172 | can get from here: 173 | 174 | https://www.varnish-cache.org 175 | 176 | The compilation of varnish is similar to this package. Please refer to the 177 | varnish documentation for all the options, but briefly, it is:: 178 | 179 | $ ./autogen.sh 180 | $ ./configure 181 | $ make 182 | 183 | If you received this packge without a pre-generated configure script, you will 184 | have to generate it using 'autogen.sh'. Otherwise, you can move straight on to 185 | the 'configure' section under Usage. 186 | 187 | Usage:: 188 | 189 | # Generate configure script 190 | ./autogen.sh 191 | 192 | # Execute configure script 193 | ./configure VARNISHSRC=DIR [VMODDIR=DIR] 194 | 195 | `VARNISHSRC` is the directory of the Varnish source tree for which to 196 | compile your vmod. Both the `VARNISHSRC` and `VARNISHSRC/include` 197 | will be added to the include search paths for your module. 198 | 199 | Optionally you can also set the vmod install directory by adding 200 | `VMODDIR=DIR` (defaults to the pkg-config discovered directory from your 201 | Varnish installation). 202 | 203 | Make targets: 204 | 205 | * make - builds the vmod 206 | * make install - installs your vmod in `VMODDIR` 207 | * make check - runs the unit tests in ``src/tests/*.vtc`` 208 | 209 | 210 | SEE ALSO 211 | ======== 212 | 213 | * https://github.com/etsy/statsd 214 | * https://www.varnish-cache.org 215 | * http://jiboumans.wordpress.com/2013/02/27/realtime-stats-from-varnish/ 216 | * https://gist.github.com/jib/5034755 217 | 218 | COPYRIGHT 219 | ========= 220 | 221 | This document is licensed under the same license as the 222 | libvmod-statsd project. See LICENSE for details. 223 | 224 | * Copyright (c) 2012 Jos Boumans 225 | -------------------------------------------------------------------------------- /autogen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | warn() { 4 | echo "WARNING: $@" 1>&2 5 | } 6 | 7 | case `uname -s` in 8 | Darwin) 9 | LIBTOOLIZE=glibtoolize 10 | ;; 11 | FreeBSD) 12 | LIBTOOLIZE=libtoolize 13 | ;; 14 | Linux) 15 | LIBTOOLIZE=libtoolize 16 | ;; 17 | SunOS) 18 | LIBTOOLIZE=libtoolize 19 | ;; 20 | *) 21 | warn "unrecognized platform:" `uname -s` 22 | LIBTOOLIZE=libtoolize 23 | esac 24 | 25 | automake_version=`automake --version | tr ' ' '\n' | egrep '^[0-9]\.[0-9a-z.-]+'` 26 | if [ -z "$automake_version" ] ; then 27 | warn "unable to determine automake version" 28 | else 29 | case $automake_version in 30 | 0.*|1.[0-8]|1.[0-8][.-]*) 31 | warn "automake ($automake_version) detected; 1.9 or newer recommended" 32 | ;; 33 | *) 34 | ;; 35 | esac 36 | fi 37 | 38 | set -ex 39 | 40 | aclocal -I m4 41 | $LIBTOOLIZE --copy --force 42 | autoheader 43 | automake --add-missing --copy --foreign 44 | autoconf 45 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | AC_PREREQ(2.59) 2 | AC_COPYRIGHT([Copyright (c) 2011 Varnish Software AS]) 3 | AC_INIT([libvmod-statsd], [trunk]) 4 | AC_CONFIG_MACRO_DIR([m4]) 5 | AC_CONFIG_SRCDIR(src/vmod_statsd.vcc) 6 | AM_CONFIG_HEADER(config.h) 7 | 8 | AC_CANONICAL_SYSTEM 9 | AC_LANG(C) 10 | 11 | AM_INIT_AUTOMAKE([foreign]) 12 | 13 | AC_GNU_SOURCE 14 | AC_PROG_CC 15 | AC_PROG_CC_STDC 16 | if test "x$ac_cv_prog_cc_c99" = xno; then 17 | AC_MSG_ERROR([Could not find a C99 compatible compiler]) 18 | fi 19 | AC_PROG_CPP 20 | 21 | AC_PROG_INSTALL 22 | AC_PROG_LIBTOOL 23 | AC_PROG_MAKE_SET 24 | 25 | # Check for rst utilities 26 | AC_CHECK_PROGS(RST2MAN, [rst2man rst2man.py], "no") 27 | if test "x$RST2MAN" = "xno"; then 28 | AC_MSG_WARN([rst2man not found - not building man pages]) 29 | fi 30 | AM_CONDITIONAL(HAVE_RST2MAN, [test "x$RST2MAN" != "xno"]) 31 | 32 | # Check for pkg-config 33 | PKG_PROG_PKG_CONFIG 34 | 35 | # Checks for header files. 36 | AC_HEADER_STDC 37 | AC_CHECK_HEADERS([sys/stdlib.h]) 38 | 39 | # Check for python 40 | AC_CHECK_PROGS(PYTHON, [python3 python3.1 python3.2 python2.7 python2.6 python2.5 python2 python], [AC_MSG_ERROR([Python is needed to build this vmod, please install python.])]) 41 | 42 | # Varnish source tree 43 | AC_ARG_VAR([VARNISHSRC], [path to Varnish source tree (mandatory)]) 44 | if test "x$VARNISHSRC" = x; then 45 | AC_MSG_ERROR([No Varnish source tree specified]) 46 | fi 47 | VARNISHSRC=`cd $VARNISHSRC && pwd` 48 | AC_CHECK_FILE([$VARNISHSRC/include/varnishapi.h], 49 | [], 50 | [AC_MSG_FAILURE(["$VARNISHSRC" is not a Varnish source directory])] 51 | ) 52 | AM_CONDITIONAL(HAVE_VARNISHSRC, [test -n $VARNISHSRC]) 53 | 54 | # Check that varnishtest is built in the varnish source directory 55 | AC_CHECK_FILE([$VARNISHSRC/bin/varnishtest/varnishtest], 56 | [], 57 | [AC_MSG_FAILURE([Can't find "$VARNISHSRC/bin/varnishtest/varnishtest". Please build your varnish source directory])] 58 | ) 59 | 60 | # vmod installation dir 61 | AC_ARG_VAR([VMODDIR], [vmod installation directory @<:@LIBDIR/varnish/vmods@:>@]) 62 | if test "x$VMODDIR" = x; then 63 | VMODDIR=`pkg-config --variable=vmoddir varnishapi` 64 | if test "x$VMODDIR" = x; then 65 | AC_MSG_FAILURE([Can't determine vmod installation directory]) 66 | fi 67 | fi 68 | AM_CONDITIONAL(HAVE_VMODDIR, [test -n $VMODDIR]) 69 | 70 | AC_CONFIG_FILES([ 71 | Makefile 72 | src/Makefile 73 | ]) 74 | AC_OUTPUT 75 | -------------------------------------------------------------------------------- /m4/PLACEHOLDER: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jib/libvmod-statsd/de088009e0020b5a04161663693360a0d20289cc/m4/PLACEHOLDER -------------------------------------------------------------------------------- /src/Makefile.am: -------------------------------------------------------------------------------- 1 | # if VARNISHSRC is defined on the command-line, use that. Otherwise, build 2 | # this the same as the modules that come with varnish (i.e. we're building 3 | # within the varnish src dir itself, and $(top_srcdir) is the varnish source). 4 | # 5 | if HAVE_VARNISHSRC 6 | SRC = $(VARNISHSRC) 7 | DIR_PREFIX = / 8 | else 9 | SRC = $(top_srcdir) 10 | DIR_PREFIX = lib/libvmod-statsd/ 11 | endif 12 | 13 | INCLUDES = -I$(SRC)/include -I$(SRC) 14 | 15 | if HAVE_VMODDIR 16 | vmoddir = $(VMODDIR) 17 | else 18 | vmoddir = $(pkglibdir)/vmods 19 | endif 20 | 21 | vmod_LTLIBRARIES = libvmod_statsd.la 22 | 23 | libvmod_statsd_la_LDFLAGS = -module -export-dynamic -avoid-version 24 | 25 | libvmod_statsd_la_SOURCES = \ 26 | vcc_if.c \ 27 | vcc_if.h \ 28 | vmod_statsd.c 29 | 30 | vcc_if.c vcc_if.h: $(SRC)/lib/libvmod_std/vmod.py $(top_srcdir)/$(DIR_PREFIX)src/vmod_statsd.vcc 31 | @PYTHON@ $(SRC)/lib/libvmod_std/vmod.py $(top_srcdir)/$(DIR_PREFIX)src/vmod_statsd.vcc 32 | 33 | VMOD_TESTS = tests/*.vtc 34 | .PHONY: $(VMOD_TESTS) 35 | 36 | tests/*.vtc: 37 | $(SRC)/bin/varnishtest/varnishtest -Dvarnishd=$(SRC)/bin/varnishd/varnishd -Dvmod_topbuild=$(abs_top_builddir) $@ 38 | 39 | check: $(VMOD_TESTS) 40 | 41 | EXTRA_DIST = \ 42 | vmod_statsd.vcc \ 43 | $(VMOD_TESTS) 44 | 45 | CLEANFILES = $(builddir)/vcc_if.c $(builddir)/vcc_if.h 46 | -------------------------------------------------------------------------------- /src/tests/test01.vtc: -------------------------------------------------------------------------------- 1 | varnishtest "Test statsd vmod" 2 | 3 | server s1 { 4 | rxreq 5 | txresp 6 | } -start 7 | 8 | # XXX I don't know how to test reception of UDP packets from varnish, 9 | # so for now, these tests just make sure that the process doesn't crash, 10 | # hang, or do anything else bad 11 | 12 | varnish v1 -vcl+backend { 13 | import statsd from "${vmod_topbuild}/src/.libs/libvmod_statsd.so"; 14 | 15 | sub vcl_init { 16 | statsd.server("localhost", "1"); # nothing listens on UDP port 1 right? 17 | statsd.prefix("prefix"); 18 | statsd.suffix("suffix"); 19 | } 20 | 21 | sub vcl_deliver { 22 | statsd.incr( "incr" ); 23 | statsd.gauge( "gauge", 1234567890 ); 24 | statsd.timing( "timing", 1234567890 ); 25 | statsd.counter( "counter", 1234567890 ); 26 | 27 | ### NULL pointers used to crash the module. Verify that bug 28 | ### is gone. 29 | statsd.incr( resp.http.x-does-not-exist ); 30 | statsd.gauge( resp.http.x-does-not-exist, 1234567890 ); 31 | statsd.timing( resp.http.x-does-not-exist, 1234567890 ); 32 | statsd.counter( resp.http.x-does-not-exist, 1234567890 ); 33 | 34 | # So we know we got to at least here. 35 | set resp.http.hello = "Hello, World"; 36 | } 37 | 38 | } -start 39 | 40 | client c1 { 41 | txreq -url "/" 42 | rxresp 43 | expect resp.http.hello == "Hello, World" 44 | } 45 | 46 | client c1 -run 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/vmod_statsd.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "vrt.h" 6 | #include "bin/varnishd/cache.h" 7 | 8 | #include "vcc_if.h" 9 | 10 | // Socket related libraries 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #define BUF_SIZE 500 19 | 20 | //#define DEBUG 1 21 | 22 | #ifdef DEBUG // To print diagnostics to the error log 23 | #define _DEBUG 1 // enable through gcc -DDEBUG 24 | #else 25 | #define _DEBUG 0 26 | #endif 27 | 28 | typedef struct statsdConfig { 29 | char *host; // statsd host 30 | char *port; // statsd port - as STRING 31 | char *prefix; // prefix any key with this 32 | char *suffix; // suffix any key with this 33 | int socket; // open socket to the daemon 34 | } config_t; 35 | 36 | 37 | // ****************************** 38 | // Utility functions 39 | // ****************************** 40 | 41 | // Unfortunately std.fileread() will append a newline, even if there is none in 42 | // the file that was being read. So if you use that (as we suggest in the docs) 43 | // to set prefix/suffix, you'll be in for a nasty surprise. 44 | char * 45 | _strip_newline( char *line ) { 46 | char *pos; 47 | 48 | if( (pos = strchr( line, '\n' )) != NULL ) { 49 | *pos = '\0'; 50 | } 51 | 52 | if( (pos = strchr( line, '\r' )) != NULL ) { 53 | *pos = '\0'; 54 | } 55 | 56 | //_DEBUG && fprintf( stderr, "vmod-statsd: stripping new lines. New string: %s\n", line ); 57 | 58 | 59 | return line; 60 | } 61 | 62 | // ****************************** 63 | // Configuration 64 | // ****************************** 65 | 66 | static void 67 | free_function(void *priv) { 68 | config_t *cfg = priv; 69 | if( cfg->socket > 0 ) { 70 | _DEBUG && fprintf( stderr, "vmod-statsd: free: Closing socket with FD %d\n", cfg->socket ); 71 | int close_ret = close( cfg->socket ); 72 | if( close_ret != 0 ) { 73 | int close_error = errno; 74 | _DEBUG && fprintf( stderr, "vmod-statsd: free: Error closing socket: %s (errno %d)\n", 75 | strerror(close_error), close_error ); 76 | } 77 | cfg->socket = 0; 78 | _DEBUG && fprintf( stderr, "vmod-statsd: free: Socket closed/reset\n" ); 79 | } 80 | } 81 | 82 | int 83 | init_function(struct vmod_priv *priv, const struct VCL_conf *conf) { 84 | 85 | // ****************************** 86 | // Configuration defaults 87 | // ****************************** 88 | 89 | config_t *cfg; 90 | cfg = malloc(sizeof(config_t)); 91 | cfg->host = "localhost"; 92 | cfg->port = "8125"; 93 | cfg->prefix = ""; 94 | cfg->suffix = ""; 95 | cfg->socket = 0; 96 | 97 | _DEBUG && fprintf( stderr, "vmod-statsd: init: configuration initialized\n" ); 98 | 99 | // ****************************** 100 | // Store the config 101 | // ****************************** 102 | 103 | priv->priv = cfg; 104 | priv->free = free_function; 105 | 106 | return (0); 107 | } 108 | 109 | /** The following may ONLY be called from VCL_init **/ 110 | void 111 | vmod_prefix( struct sess *sp, struct vmod_priv *priv, const char *prefix ) { 112 | config_t *cfg = priv->priv; 113 | cfg->prefix = _strip_newline( strdup( prefix ) ); 114 | } 115 | 116 | /** The following may ONLY be called from VCL_init **/ 117 | void 118 | vmod_suffix( struct sess *sp, struct vmod_priv *priv, const char *suffix ) { 119 | 120 | config_t *cfg = priv->priv; 121 | cfg->suffix = _strip_newline( strdup( suffix ) ); 122 | } 123 | 124 | /** The following may ONLY be called from VCL_init **/ 125 | void 126 | vmod_server( struct sess *sp, struct vmod_priv *priv, const char *host, const char *port ) { 127 | 128 | // ****************************** 129 | // Configuration 130 | // ****************************** 131 | 132 | config_t *cfg = priv->priv; 133 | cfg->host = strdup( host ); 134 | cfg->port = strdup( port ); 135 | } 136 | 137 | 138 | // ****************************** 139 | // Connect to the remote socket 140 | // ****************************** 141 | 142 | int 143 | _connect_to_statsd( struct vmod_priv *priv ) { 144 | config_t *cfg = priv->priv; 145 | 146 | // Grab 2 structs for the connection 147 | struct addrinfo *statsd; /* will allocated by getaddrinfo */ 148 | struct addrinfo *hints; 149 | hints = malloc(sizeof(struct addrinfo)); 150 | 151 | if (hints == NULL) { 152 | fprintf( stderr, "vmod-statsd: malloc failed for hints addrinfo struct\n" ); 153 | return -1; 154 | } 155 | 156 | // Hints can be full of garbage, and it's lead to segfaults inside 157 | // Varnish. See this issue: https://github.com/jib/libvmod-statsd/pull/5/files 158 | // As a way to fix that, we zero out the memory, as also pointed out 159 | // in the freeaddrinfo manpage: http://linux.die.net/man/3/freeaddrinfo 160 | memset( hints, 0, sizeof(struct addrinfo) ); 161 | 162 | // what type of socket is the statsd endpoint? 163 | hints->ai_family = AF_UNSPEC; 164 | hints->ai_socktype = SOCK_DGRAM; 165 | hints->ai_protocol = IPPROTO_UDP; 166 | hints->ai_flags = 0; 167 | 168 | _DEBUG && fprintf( stderr, "vmod-statsd: socket hints compiled\n" ); 169 | 170 | // using getaddrinfo lets us use a hostname, rather than an 171 | // ip address. 172 | int err; 173 | if( (err = getaddrinfo( cfg->host, cfg->port, hints, &statsd )) != 0 ) { 174 | fprintf( stderr, "vmod-statsd: getaddrinfo on %s:%s failed: %s\n", 175 | cfg->host, cfg->port, gai_strerror(err) ); 176 | 177 | freeaddrinfo( hints ); 178 | return -1; 179 | } 180 | 181 | _DEBUG && fprintf( stderr, "vmod-statsd: getaddrinfo completed\n" ); 182 | 183 | // ****************************** 184 | // Store the open connection 185 | // ****************************** 186 | 187 | // getaddrinfo() may return more than one address structure 188 | // but since this is UDP, we can't verify the connection 189 | // anyway, so we will just use the first one 190 | cfg->socket = socket( statsd->ai_family, statsd->ai_socktype, 191 | statsd->ai_protocol ); 192 | 193 | if( cfg->socket == -1 ) { 194 | _DEBUG && fprintf( stderr, "vmod-statsd: socket creation failed\n" ); 195 | close( cfg->socket ); 196 | freeaddrinfo( statsd ); 197 | freeaddrinfo( hints ); 198 | return -1; 199 | } 200 | 201 | _DEBUG && fprintf( stderr, "vmod-statsd: socket opened: %d\n", cfg->socket ); 202 | 203 | // connection failed.. for some reason... 204 | if( connect( cfg->socket, statsd->ai_addr, statsd->ai_addrlen ) == -1 ) { 205 | _DEBUG && fprintf( stderr, "vmod-statsd: socket connection failed\n" ); 206 | close( cfg->socket ); 207 | 208 | freeaddrinfo( statsd ); 209 | freeaddrinfo( hints ); 210 | return -1; 211 | } 212 | 213 | _DEBUG && fprintf( stderr, "vmod-statsd: socket connected\n" ); 214 | 215 | // now that we have an outgoing socket, we don't need this 216 | // anymore. 217 | freeaddrinfo( statsd ); 218 | freeaddrinfo( hints ); 219 | 220 | _DEBUG && fprintf( stderr, "vmod-statsd: statsd server: %s:%s (fd: %d)\n", 221 | cfg->host, cfg->port, cfg->socket ); 222 | 223 | return cfg->socket; 224 | } 225 | 226 | int 227 | _send_to_statsd( struct vmod_priv *priv, const char *key, const char *val ) { 228 | _DEBUG && fprintf( stderr, "vmod-statsd: pre config\n" ); 229 | 230 | config_t *cfg = priv->priv; 231 | 232 | _DEBUG && fprintf( stderr, "vmod-statsd: post config\n" ); 233 | 234 | // If you are using some empty key, bail - this can happen if you use 235 | // say: statsd.incr( req.http.x-does-not-exist ). Rather than getting 236 | // and empty string, we get a null pointer. 237 | if( key == NULL || val == NULL ) { 238 | _DEBUG && fprintf( stderr, "vmod-statsd: Key or value is NULL pointer - ignoring\n" ); 239 | return -1; 240 | } 241 | 242 | _DEBUG && fprintf( stderr, "vmod-statsd: pre stat composition\n" ); 243 | 244 | // Enough room for the key/val plus prefix/suffix plus newline plus a null byte. 245 | char stat[ strlen(key) + strlen(val) + 246 | strlen(cfg->prefix) + strlen(cfg->suffix) + 1 ]; 247 | 248 | strncpy( stat, cfg->prefix, strlen(cfg->prefix) + 1 ); 249 | strncat( stat, key, strlen(key) + 1 ); 250 | strncat( stat, cfg->suffix, strlen(cfg->suffix) + 1 ); 251 | strncat( stat, val, strlen(val) + 1 ); 252 | 253 | _DEBUG && fprintf( stderr, "vmod-statsd: post stat composition: %s\n", stat ); 254 | 255 | // Newer versions of statsd allow multiple metrics in a single packet, delimited 256 | // by newlines. That unfortunately means that if we end our message with a new 257 | // line, statsd will interpret this as an empty second metric and log a 'bad line'. 258 | // This is true in at least version 0.5.0 and to avoid that, we don't send the 259 | // newline. Makes debugging using nc -klu 8125 a bit more tricky, but works with 260 | // modern statsds. 261 | //strncat( stat, "\n", 1 ); 262 | 263 | //_DEBUG && fprintf( stderr, "vmod-statsd: send: %s:%s %s\n", cfg->host, cfg->port, stat ); 264 | 265 | // ****************************** 266 | // Sanity checks 267 | // ****************************** 268 | 269 | _DEBUG && fprintf( stderr, "vmod-statsd: pre stat length\n" ); 270 | 271 | int len = strlen( stat ); 272 | 273 | _DEBUG && fprintf( stderr, "vmod-statsd: stat length: %d\n", len ); 274 | 275 | // +1 for the null byte 276 | if( len + 1 >= BUF_SIZE ) { 277 | _DEBUG && fprintf( stderr, "vmod-statsd: Message length %d > max length %d - ignoring\n", 278 | len, BUF_SIZE ); 279 | return -1; 280 | } 281 | 282 | // ****************************** 283 | // Send the packet 284 | // ****************************** 285 | 286 | _DEBUG && fprintf( stderr, "vmod-statsd: Checking for existing socket (%d)\n", cfg->socket ); 287 | 288 | // we may not have connected yet - in that case, do it now 289 | int sock = cfg->socket > 0 ? cfg->socket : _connect_to_statsd( priv ); 290 | 291 | // If we didn't get a socket, don't bother trying to send 292 | if( sock == -1 ) { 293 | _DEBUG && fprintf( stderr, "vmod-statsd: Could not get socket for %s\n", stat ); 294 | return -1; 295 | } 296 | 297 | // Send the stat 298 | int sent = write( sock, stat, len ); 299 | 300 | _DEBUG && fprintf( stderr, "vmod-statsd: Sent %d of %d bytes to FD %d\n", sent, len, sock ); 301 | 302 | // An error occurred - unset the socket so that the next write may try again 303 | if( sent != len ) { 304 | int write_error = errno; 305 | _DEBUG && fprintf( stderr, "vmod-statsd: Could not write stat '%s': %s (errno %d)\n", 306 | stat, strerror(write_error), write_error ); 307 | 308 | // if the write_error is not due to a bad file descriptor, try to close the socket first 309 | if( write_error != 9 ) { 310 | _DEBUG && fprintf( stderr, "vmod-statsd: Closing socket with FD: %d\n", sock ); 311 | int close_ret_val = close( sock ); 312 | if( close_ret_val != 0 ) { 313 | int close_error = errno; 314 | _DEBUG && fprintf( stderr, "vmod-statsd: Error closing socket: %s (errno %d)\n", 315 | strerror(close_error), close_error ); 316 | } 317 | } 318 | // reset the socket 319 | cfg->socket = 0; 320 | _DEBUG && fprintf( stderr, "vmod-statsd: Socket closed/reset\n" ); 321 | 322 | return -1; 323 | } 324 | 325 | return 0; 326 | } 327 | 328 | 329 | void 330 | vmod_incr( struct sess *sp, struct vmod_priv *priv, const char *key ) { 331 | _DEBUG && fprintf( stderr, "vmod-statsd: incr: %s\n", key ); 332 | 333 | // Incremenet is straight forward - just add the count + type 334 | _send_to_statsd( priv, key, ":1|c" ); 335 | } 336 | 337 | void 338 | vmod_timing( struct sess *sp, struct vmod_priv *priv, const char *key, int num ) { 339 | _DEBUG && fprintf( stderr, "vmod-statsd: timing: %s = %d\n", key, num ); 340 | 341 | // Get the buffer ready. 10 for the maximum lenghth of an int and +5 for metadata 342 | char val[ 15 ]; 343 | 344 | // looks like glork:320|ms 345 | snprintf( val, sizeof(val), ":%d|ms", num ); 346 | 347 | _send_to_statsd( priv, key, val ); 348 | } 349 | 350 | void 351 | vmod_counter( struct sess *sp, struct vmod_priv *priv, const char *key, int num ) { 352 | _DEBUG && fprintf( stderr, "vmod-statsd: counter: %s = %d\n", key, num ); 353 | 354 | // Get the buffer ready. 10 for the maximum lenghth of an int and +5 for metadata 355 | char val[ 15 ]; 356 | 357 | // looks like: gorets:42|c 358 | snprintf( val, sizeof(val), ":%d|c", num ); 359 | 360 | _send_to_statsd( priv, key, val ); 361 | } 362 | 363 | void 364 | vmod_gauge( struct sess *sp, struct vmod_priv *priv, const char *key, int num ) { 365 | _DEBUG && fprintf( stderr, "vmod-statsd: gauge: %s = %d\n", key, num ); 366 | 367 | // Get the buffer ready. 10 for the maximum lenghth of an int and +5 for metadata 368 | char val[ 15 ]; 369 | 370 | // looks like: gaugor:333|g 371 | snprintf( val, sizeof(val), ":%d|g", num ); 372 | 373 | _send_to_statsd( priv, key, val ); 374 | } 375 | 376 | 377 | // const char * 378 | // vmod_hello(struct sess *sp, const char *name) 379 | // { 380 | // char *p; 381 | // unsigned u, v; 382 | // 383 | // u = WS_Reserve(sp->wrk->ws, 0); /* Reserve some work space */ 384 | // p = sp->wrk->ws->f; /* Front of workspace area */ 385 | // v = snprintf(p, u, "Hello, %s", name); 386 | // v++; 387 | // if (v > u) { 388 | // /* No space, reset and leave */ 389 | // WS_Release(sp->wrk->ws, 0); 390 | // return (NULL); 391 | // } 392 | // /* Update work space with what we've used */ 393 | // WS_Release(sp->wrk->ws, v); 394 | // return (p); 395 | // } 396 | 397 | // _DEBUG && fprintf( stderr, "vmod-statsd: Open: %.9f Req: %.9f Res: %.9f End: %.9f\n", 398 | // sp->t_open, sp->t_req, sp->t_resp, sp->t_end ); 399 | 400 | -------------------------------------------------------------------------------- /src/vmod_statsd.vcc: -------------------------------------------------------------------------------- 1 | Module statsd 2 | Init init_function 3 | Function VOID server( PRIV_VCL, STRING, STRING ) 4 | Function VOID prefix( PRIV_VCL, STRING ) 5 | Function VOID suffix( PRIV_VCL, STRING ) 6 | Function VOID incr( PRIV_VCL, STRING ) 7 | Function VOID gauge( PRIV_VCL, STRING, INT ) 8 | Function VOID timing( PRIV_VCL, STRING, INT ) 9 | Function VOID counter( PRIV_VCL, STRING, INT ) 10 | --------------------------------------------------------------------------------