1414# statsd = Statsd.new('localhost').tap{|sd| sd.namespace = 'account'}
1515# statsd.increment 'activate'
1616class Statsd
17- class RubyUdpClient
18- attr_reader :key , : sock
17+ class UDPClient
18+ attr_reader :sock
1919
20- def initialize ( address , port , key = nil )
20+ def initialize ( address , port = nil )
21+ address , port = address . split ( ':' ) if address . include? ( ':' )
2122 addrinfo = Addrinfo . ip ( address )
23+
2224 @sock = UDPSocket . new ( addrinfo . pfamily )
2325 @sock . connect ( addrinfo . ip_address , port )
24- @key = key
2526 end
2627
2728 def send ( msg )
2829 sock . write ( msg )
29- rescue => boom
30+ rescue SystemCallError
3031 nil
3132 end
3233 end
3334
35+ class SecureUDPClient < UDPClient
36+ def initialize ( address , port , key )
37+ super ( address , port )
38+ @key = key
39+ end
40+
41+ def send ( msg )
42+ super ( signed_payload ( msg ) )
43+ end
44+
45+ private
46+ # defer loading openssl and securerandom unless needed. this shaves ~10ms off
47+ # of baseline require load time for environments that don't require message signing.
48+ def self . setup_openssl
49+ @sha256 ||= begin
50+ require 'securerandom'
51+ require 'openssl'
52+ OpenSSL ::Digest ::SHA256 . new
53+ end
54+ end
55+
56+ def signed_payload ( message )
57+ sha256 = SecureUDPClient . setup_openssl
58+ payload = timestamp + nonce + message
59+ signature = OpenSSL ::HMAC . digest ( sha256 , @key , payload )
60+ signature + payload
61+ end
62+
63+ def timestamp
64+ [ Time . now . to_i ] . pack ( "Q<" )
65+ end
66+
67+ def nonce
68+ SecureRandom . random_bytes ( 4 )
69+ end
70+ end
71+
3472 # A namespace to prepend to all statsd calls.
3573 attr_reader :namespace
3674
@@ -52,20 +90,38 @@ def namespace=(namespace)
5290
5391 def initialize ( client_class = nil )
5492 @shards = [ ]
55- @client_class = client_class || RubyUdpClient
93+ @client_class = client_class || UDPClient
5694 self . namespace = nil
5795 end
5896
5997 def self . simple ( addr , port = nil )
6098 self . new . add_shard ( addr , port )
6199 end
62100
63- def add_shard ( addr , port = nil , key = nil )
64- addr , port = addr . split ( ':' ) if addr . include? ( ':' )
65- @shards << @client_class . new ( addr , port . to_i , key )
101+ def add_shard ( *args )
102+ @shards << @client_class . new ( *args )
66103 self
67104 end
68105
106+ def enable_buffering ( buffer_size = nil )
107+ return if @buffering
108+ @shards . map! { |client | Buffer . new ( client , buffer_size ) }
109+ @buffering = true
110+ end
111+
112+ def disable_buffering
113+ return unless @buffering
114+ flush_all
115+ @shards . map! { |client | client . base_client }
116+ @buffering = false
117+ end
118+
119+ def flush_all
120+ return unless @buffering
121+ @shards . each { |client | client . flush }
122+ end
123+
124+
69125 # Sends an increment (count = 1) for the given stat to the statsd server.
70126 #
71127 # @param stat (see #count)
@@ -129,7 +185,6 @@ def time(stat, sample_rate=1)
129185 def histogram ( stat , value , sample_rate = 1 ) ; send stat , value , HISTOGRAM_TYPE , sample_rate end
130186
131187 private
132-
133188 def sampled ( sample_rate )
134189 yield unless sample_rate < 1 and rand > sample_rate
135190 end
@@ -140,7 +195,7 @@ def send(stat, delta, type, sample_rate=1)
140195 stat . gsub! ( /::/ , "." . freeze )
141196 stat . gsub! ( RESERVED_CHARS_REGEX , "_" . freeze )
142197
143- msg = ""
198+ msg = String . new
144199 msg << @prefix
145200 msg << stat
146201 msg << ":" . freeze
@@ -153,7 +208,7 @@ def send(stat, delta, type, sample_rate=1)
153208 end
154209
155210 shard = select_shard ( stat )
156- shard . send ( shard . key ? signed_payload ( shard . key , msg ) : msg )
211+ shard . send ( msg )
157212 end
158213 end
159214
@@ -165,28 +220,31 @@ def select_shard(stat)
165220 end
166221 end
167222
168- def signed_payload ( key , message )
169- sha256 = Statsd . setup_openssl
170- payload = timestamp + nonce + message
171- signature = OpenSSL ::HMAC . digest ( sha256 , key , payload )
172- signature + payload
173- end
223+ class Buffer
224+ DEFAULT_BUFFER_CAP = 512
225+
226+ attr_reader :base_client
227+ attr_accessor :flush_count
174228
175- # defer loading openssl and securerandom unless needed. this shaves ~10ms off
176- # of baseline require load time for environments that don't require message signing.
177- def self . setup_openssl
178- @sha256 ||= begin
179- require 'securerandom'
180- require 'openssl'
181- OpenSSL ::Digest ::SHA256 . new
229+ def initialize ( client , buffer_cap = nil )
230+ @base_client = client
231+ @buffer = String . new
232+ @buffer_cap = buffer_cap || DEFAULT_BUFFER_CAP
233+ @flush_count = 0
182234 end
183- end
184235
185- def timestamp
186- [ Time . now . to_i ] . pack ( "Q<" )
187- end
236+ def flush
237+ return unless @buffer . bytesize > 0
238+ @base_client . send ( @buffer )
239+ @buffer . clear
240+ @flush_count += 1
241+ end
188242
189- def nonce
190- SecureRandom . random_bytes ( 4 )
243+ def send ( msg )
244+ flush if @buffer . bytesize + msg . bytesize >= @buffer_cap
245+ @buffer << msg
246+ @buffer << "\n " . freeze
247+ nil
248+ end
191249 end
192250end
0 commit comments