statsd-ruby 1.1.1 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.github/workflows/ci.yml +34 -0
- data/LICENSE.txt +1 -1
- data/README.rdoc +37 -12
- data/lib/statsd-ruby.rb +1 -0
- data/lib/statsd.rb +273 -17
- data/lib/statsd/monotonic_time.rb +35 -0
- data/spec/helper.rb +12 -3
- data/spec/statsd_admin_spec.rb +117 -0
- data/spec/statsd_spec.rb +321 -71
- data/statsd-ruby.gemspec +4 -4
- metadata +106 -93
- data/.travis.yml +0 -6
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 0edd467226454eea7a63a7ed08e3468ac48a2d7438fdf8d291ab266a781c128b
|
4
|
+
data.tar.gz: 82f88de7bf7738e974a8d9f2d70f57f039d31d7f69f9b98c359b7d5c7970f93c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3520cc824d180d7304fa883fefa945d3f785d560f313c328fed8f28e4d799728c6ab61cd6b9a855d8023f644f2d328f7f6626e0af7770d8e83d99b2bb0e7a2f8
|
7
|
+
data.tar.gz: f0bc2ade747bee51cca11128a6d6ce21491b13354f519f582f90c94cd09ce1ee26372b52945c8d369dfc5160194cfd967644254350b0dd1567ff28cd64de8206
|
@@ -0,0 +1,34 @@
|
|
1
|
+
name: Ruby
|
2
|
+
|
3
|
+
on: [push, pull_request]
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
build:
|
7
|
+
strategy:
|
8
|
+
matrix:
|
9
|
+
os:
|
10
|
+
- ubuntu
|
11
|
+
- macos
|
12
|
+
ruby:
|
13
|
+
- 2.4
|
14
|
+
- 2.5
|
15
|
+
- 2.6
|
16
|
+
# TODO: - 2.7
|
17
|
+
# TODO: jruby, rbx
|
18
|
+
|
19
|
+
runs-on: ${{ matrix.os }}-latest
|
20
|
+
|
21
|
+
steps:
|
22
|
+
- uses: actions/checkout@v1
|
23
|
+
|
24
|
+
- name: Set up Ruby
|
25
|
+
uses: actions/setup-ruby@v1
|
26
|
+
with:
|
27
|
+
ruby-version: ${{ matrix.ruby }}
|
28
|
+
architecture: x64
|
29
|
+
|
30
|
+
- name: Build and test with Rake
|
31
|
+
run: |
|
32
|
+
gem install bundler
|
33
|
+
bundle install
|
34
|
+
bundle exec rake
|
data/LICENSE.txt
CHANGED
data/README.rdoc
CHANGED
@@ -1,17 +1,20 @@
|
|
1
|
-
= statsd-ruby {<img src="https://secure.travis-ci.org/reinh/statsd.
|
1
|
+
= statsd-ruby Travis: {<img src="https://secure.travis-ci.org/reinh/statsd.svg" />}[http://travis-ci.org/reinh/statsd] CI: {<img src="https://github.com/reinh/statsd/workflows/Ruby/badge.svg" />}[https://github.com/reinh/statsd/actions?query=workflow%3ARuby]
|
2
2
|
|
3
3
|
A Ruby client for {StatsD}[https://github.com/etsy/statsd]
|
4
4
|
|
5
5
|
= Installing
|
6
6
|
|
7
7
|
Bundler:
|
8
|
-
gem "statsd-ruby"
|
8
|
+
gem "statsd-ruby"
|
9
9
|
|
10
10
|
= Basic Usage
|
11
11
|
|
12
12
|
# Set up a global Statsd client for a server on localhost:9125
|
13
13
|
$statsd = Statsd.new 'localhost', 9125
|
14
14
|
|
15
|
+
# Set up a global Statsd client for a server on IPv6 port 9125
|
16
|
+
$statsd = Statsd.new '::1', 9125
|
17
|
+
|
15
18
|
# Send some stats
|
16
19
|
$statsd.increment 'garets'
|
17
20
|
$statsd.timing 'glork', 320
|
@@ -28,14 +31,16 @@ Bundler:
|
|
28
31
|
|
29
32
|
Run the specs with <tt>rake spec</tt>
|
30
33
|
|
31
|
-
Run the specs and include live integration specs with <tt>LIVE=true rake spec</tt>. Note: This will test over a real UDP socket.
|
32
|
-
|
33
34
|
= Performance
|
34
35
|
|
35
|
-
* A short note about DNS: If you use a dns name for the host option, then you will want to use a local caching dns service for
|
36
|
+
* A short note about DNS: If you use a dns name for the host option, then you will want to use a local caching dns service for optimal performance (e.g. nscd).
|
37
|
+
|
38
|
+
= Extensions / Libraries / Extra Docs
|
39
|
+
|
40
|
+
* See the wiki[https://github.com/reinh/statsd/wiki]
|
36
41
|
|
37
42
|
== Contributing to statsd
|
38
|
-
|
43
|
+
|
39
44
|
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
40
45
|
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
41
46
|
* Fork the project
|
@@ -47,15 +52,35 @@ Run the specs and include live integration specs with <tt>LIVE=true rake spec</t
|
|
47
52
|
== Contributors
|
48
53
|
|
49
54
|
* Rein Henrichs
|
50
|
-
*
|
55
|
+
* Alex Williams
|
56
|
+
* Andrew Meyer
|
57
|
+
* Chris Gaffney
|
58
|
+
* Cody Cutrer
|
59
|
+
* Corey Donohoe
|
60
|
+
* Dotan Nahum
|
61
|
+
* Erez Rabih
|
62
|
+
* Eric Chapweske
|
63
|
+
* Gabriel Burt
|
64
|
+
* Hannes Georg
|
65
|
+
* James Tucker
|
51
66
|
* Jeremy Kemper
|
67
|
+
* John Nunemaker
|
68
|
+
* Lann Martin
|
69
|
+
* Mahesh Murthy
|
70
|
+
* Manu J
|
71
|
+
* Matt Sanford
|
72
|
+
* Nate Bird
|
73
|
+
* Noah Lorang
|
74
|
+
* Oscar Del Ben
|
75
|
+
* Peter Mounce
|
76
|
+
* Ray Krueger
|
77
|
+
* Reed Lipman
|
78
|
+
* rick
|
52
79
|
* Ryan Tomayko
|
53
|
-
*
|
54
|
-
*
|
80
|
+
* Schuyler Erle
|
81
|
+
* Thomas Whaples
|
55
82
|
* Trae Robrock
|
56
|
-
* Corey Donohoe
|
57
|
-
* James Tucker
|
58
83
|
|
59
84
|
== Copyright
|
60
85
|
|
61
|
-
Copyright (c) 2011 Rein Henrichs. See LICENSE.txt for further details.
|
86
|
+
Copyright (c) 2011, 2012, 2013 Rein Henrichs. See LICENSE.txt for further details.
|
data/lib/statsd-ruby.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'statsd'
|
data/lib/statsd.rb
CHANGED
@@ -1,10 +1,15 @@
|
|
1
1
|
require 'socket'
|
2
2
|
require 'forwardable'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
require 'statsd/monotonic_time'
|
3
6
|
|
4
7
|
# = Statsd: A Statsd client (https://github.com/etsy/statsd)
|
5
8
|
#
|
6
|
-
# @example Set up a global Statsd client for a server on localhost:
|
9
|
+
# @example Set up a global Statsd client for a server on localhost:8125
|
7
10
|
# $statsd = Statsd.new 'localhost', 8125
|
11
|
+
# @example Set up a global Statsd client for a server on IPv6 port 8125
|
12
|
+
# $statsd = Statsd.new '::1', 8125
|
8
13
|
# @example Send some stats
|
9
14
|
# $statsd.increment 'garets'
|
10
15
|
# $statsd.timing 'glork', 320
|
@@ -15,8 +20,8 @@ require 'forwardable'
|
|
15
20
|
# statsd = Statsd.new('localhost').tap{|sd| sd.namespace = 'account'}
|
16
21
|
# statsd.increment 'activate'
|
17
22
|
#
|
18
|
-
# Statsd instances are thread safe for general usage, by
|
19
|
-
#
|
23
|
+
# Statsd instances are thread safe for general usage, by utilizing the thread
|
24
|
+
# safe nature of UDP sends. The attributes are stateful, and are not
|
20
25
|
# mutexed, it is expected that users will not change these at runtime in
|
21
26
|
# threaded environments. If users require such use cases, it is recommend that
|
22
27
|
# users either mutex around their Statsd object, or create separate objects for
|
@@ -43,18 +48,27 @@ class Statsd
|
|
43
48
|
|
44
49
|
extend Forwardable
|
45
50
|
def_delegators :@statsd,
|
46
|
-
:namespace, :namespace=,
|
51
|
+
:namespace, :namespace=,
|
52
|
+
:host, :host=,
|
53
|
+
:port, :port=,
|
54
|
+
:prefix,
|
55
|
+
:postfix,
|
56
|
+
:delimiter, :delimiter=
|
47
57
|
|
48
|
-
attr_accessor :batch_size
|
58
|
+
attr_accessor :batch_size, :batch_byte_size, :flush_interval
|
49
59
|
|
50
|
-
# @param [Statsd] requires a configured Statsd instance
|
60
|
+
# @param [Statsd] statsd requires a configured Statsd instance
|
51
61
|
def initialize(statsd)
|
52
62
|
@statsd = statsd
|
53
63
|
@batch_size = statsd.batch_size
|
64
|
+
@batch_byte_size = statsd.batch_byte_size
|
65
|
+
@flush_interval = statsd.flush_interval
|
54
66
|
@backlog = []
|
67
|
+
@backlog_bytesize = 0
|
68
|
+
@last_flush = Time.now
|
55
69
|
end
|
56
70
|
|
57
|
-
# @
|
71
|
+
# @yield [Batch] yields itself
|
58
72
|
#
|
59
73
|
# A convenience method to ensure that data is not lost in the event of an
|
60
74
|
# exception being thrown. Batches will be transmitted on the parent socket
|
@@ -69,20 +83,178 @@ class Statsd
|
|
69
83
|
unless @backlog.empty?
|
70
84
|
@statsd.send_to_socket @backlog.join("\n")
|
71
85
|
@backlog.clear
|
86
|
+
@backlog_bytesize = 0
|
87
|
+
@last_flush = Time.now
|
72
88
|
end
|
73
89
|
end
|
74
90
|
|
75
91
|
protected
|
76
92
|
|
77
93
|
def send_to_socket(message)
|
94
|
+
# this message wouldn't fit; flush the queue. note that we don't have
|
95
|
+
# to do this for message based flushing, because we're incrementing by
|
96
|
+
# one, so the post-queue check will always catch it
|
97
|
+
if (@batch_byte_size && @backlog_bytesize + message.bytesize + 1 > @batch_byte_size) ||
|
98
|
+
(@flush_interval && last_flush_seconds_ago >= @flush_interval)
|
99
|
+
flush
|
100
|
+
end
|
78
101
|
@backlog << message
|
79
|
-
|
102
|
+
@backlog_bytesize += message.bytesize
|
103
|
+
# skip the interleaved newline for the first item
|
104
|
+
@backlog_bytesize += 1 if @backlog.length != 1
|
105
|
+
# if we're precisely full now, flush
|
106
|
+
if (@batch_size && @backlog.size == @batch_size) ||
|
107
|
+
(@batch_byte_size && @backlog_bytesize == @batch_byte_size)
|
80
108
|
flush
|
81
109
|
end
|
82
110
|
end
|
83
111
|
|
112
|
+
def last_flush_seconds_ago
|
113
|
+
Time.now - @last_flush
|
114
|
+
end
|
115
|
+
|
84
116
|
end
|
85
117
|
|
118
|
+
class Admin
|
119
|
+
# StatsD host. Defaults to 127.0.0.1.
|
120
|
+
attr_reader :host
|
121
|
+
|
122
|
+
# StatsD admin port. Defaults to 8126.
|
123
|
+
attr_reader :port
|
124
|
+
|
125
|
+
class << self
|
126
|
+
# Set to a standard logger instance to enable debug logging.
|
127
|
+
attr_accessor :logger
|
128
|
+
end
|
129
|
+
|
130
|
+
# @attribute [w] host.
|
131
|
+
# Users should call connect after changing this.
|
132
|
+
def host=(host)
|
133
|
+
@host = host || '127.0.0.1'
|
134
|
+
end
|
135
|
+
|
136
|
+
# @attribute [w] port.
|
137
|
+
# Users should call connect after changing this.
|
138
|
+
def port=(port)
|
139
|
+
@port = port || 8126
|
140
|
+
end
|
141
|
+
|
142
|
+
# @param [String] host your statsd host
|
143
|
+
# @param [Integer] port your statsd port
|
144
|
+
def initialize(host = '127.0.0.1', port = 8126)
|
145
|
+
@host = host || '127.0.0.1'
|
146
|
+
@port = port || 8126
|
147
|
+
# protects @socket transactions
|
148
|
+
@socket = nil
|
149
|
+
@s_mu = Mutex.new
|
150
|
+
connect
|
151
|
+
end
|
152
|
+
|
153
|
+
# Reads all gauges from StatsD.
|
154
|
+
def gauges
|
155
|
+
read_metric :gauges
|
156
|
+
end
|
157
|
+
|
158
|
+
# Reads all timers from StatsD.
|
159
|
+
def timers
|
160
|
+
read_metric :timers
|
161
|
+
end
|
162
|
+
|
163
|
+
# Reads all counters from StatsD.
|
164
|
+
def counters
|
165
|
+
read_metric :counters
|
166
|
+
end
|
167
|
+
|
168
|
+
# @param[String] item
|
169
|
+
# Deletes one or more gauges. Wildcards are allowed.
|
170
|
+
def delgauges item
|
171
|
+
delete_metric :gauges, item
|
172
|
+
end
|
173
|
+
|
174
|
+
# @param[String] item
|
175
|
+
# Deletes one or more timers. Wildcards are allowed.
|
176
|
+
def deltimers item
|
177
|
+
delete_metric :timers, item
|
178
|
+
end
|
179
|
+
|
180
|
+
# @param[String] item
|
181
|
+
# Deletes one or more counters. Wildcards are allowed.
|
182
|
+
def delcounters item
|
183
|
+
delete_metric :counters, item
|
184
|
+
end
|
185
|
+
|
186
|
+
def stats
|
187
|
+
result = @s_mu.synchronize do
|
188
|
+
# the format of "stats" isn't JSON, who knows why
|
189
|
+
send_to_socket "stats"
|
190
|
+
read_from_socket
|
191
|
+
end
|
192
|
+
items = {}
|
193
|
+
result.split("\n").each do |line|
|
194
|
+
key, val = line.chomp.split(": ")
|
195
|
+
items[key] = val.to_i
|
196
|
+
end
|
197
|
+
items
|
198
|
+
end
|
199
|
+
|
200
|
+
# Reconnects the socket, for when the statsd address may have changed. Users
|
201
|
+
# do not normally need to call this, but calling it may be appropriate when
|
202
|
+
# reconfiguring a process (e.g. from HUP)
|
203
|
+
def connect
|
204
|
+
@s_mu.synchronize do
|
205
|
+
begin
|
206
|
+
@socket.flush rescue nil
|
207
|
+
@socket.close if @socket
|
208
|
+
rescue
|
209
|
+
# Ignore socket errors on close.
|
210
|
+
end
|
211
|
+
@socket = TCPSocket.new(host, port)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
private
|
216
|
+
|
217
|
+
def read_metric name
|
218
|
+
result = @s_mu.synchronize do
|
219
|
+
send_to_socket name
|
220
|
+
read_from_socket
|
221
|
+
end
|
222
|
+
# for some reason, the reply looks like JSON, but isn't, quite
|
223
|
+
JSON.parse result.gsub("'", "\"")
|
224
|
+
end
|
225
|
+
|
226
|
+
def delete_metric name, item
|
227
|
+
result = @s_mu.synchronize do
|
228
|
+
send_to_socket "del#{name} #{item}"
|
229
|
+
read_from_socket
|
230
|
+
end
|
231
|
+
deleted = []
|
232
|
+
result.split("\n").each do |line|
|
233
|
+
deleted << line.chomp.split(": ")[-1]
|
234
|
+
end
|
235
|
+
deleted
|
236
|
+
end
|
237
|
+
|
238
|
+
def send_to_socket(message)
|
239
|
+
self.class.logger.debug { "Statsd: #{message}" } if self.class.logger
|
240
|
+
@socket.write(message.to_s + "\n")
|
241
|
+
rescue => boom
|
242
|
+
self.class.logger.error { "Statsd: #{boom.class} #{boom}" } if self.class.logger
|
243
|
+
nil
|
244
|
+
end
|
245
|
+
|
246
|
+
|
247
|
+
def read_from_socket
|
248
|
+
buffer = ""
|
249
|
+
loop do
|
250
|
+
line = @socket.readline
|
251
|
+
break if line == "END\n"
|
252
|
+
buffer += line
|
253
|
+
end
|
254
|
+
@socket.readline # clear the closing newline out of the socket
|
255
|
+
buffer
|
256
|
+
end
|
257
|
+
end
|
86
258
|
|
87
259
|
# A namespace to prepend to all statsd calls.
|
88
260
|
attr_reader :namespace
|
@@ -96,12 +268,21 @@ class Statsd
|
|
96
268
|
# StatsD namespace prefix, generated from #namespace
|
97
269
|
attr_reader :prefix
|
98
270
|
|
99
|
-
# The default batch size for new batches (default: 10)
|
271
|
+
# The default batch size for new batches. Set to nil to use batch_byte_size (default: 10)
|
100
272
|
attr_accessor :batch_size
|
101
273
|
|
274
|
+
# The default batch size, in bytes, for new batches (default: default nil; use batch_size)
|
275
|
+
attr_accessor :batch_byte_size
|
276
|
+
|
277
|
+
# The flush interval, in seconds, for new batches (default: nil)
|
278
|
+
attr_accessor :flush_interval
|
279
|
+
|
102
280
|
# a postfix to append to all metrics
|
103
281
|
attr_reader :postfix
|
104
282
|
|
283
|
+
# The replacement of :: on ruby module names when transformed to statsd metric names
|
284
|
+
attr_reader :delimiter
|
285
|
+
|
105
286
|
class << self
|
106
287
|
# Set to a standard logger instance to enable debug logging.
|
107
288
|
attr_accessor :logger
|
@@ -109,11 +290,20 @@ class Statsd
|
|
109
290
|
|
110
291
|
# @param [String] host your statsd host
|
111
292
|
# @param [Integer] port your statsd port
|
112
|
-
|
113
|
-
|
293
|
+
# @param [Symbol] protocol :tcp for TCP, :udp or any other value for UDP
|
294
|
+
def initialize(host = '127.0.0.1', port = 8125, protocol = :udp)
|
295
|
+
@host = host || '127.0.0.1'
|
296
|
+
@port = port || 8125
|
297
|
+
self.delimiter = "."
|
114
298
|
@prefix = nil
|
115
299
|
@batch_size = 10
|
300
|
+
@batch_byte_size = nil
|
301
|
+
@flush_interval = nil
|
116
302
|
@postfix = nil
|
303
|
+
@socket = nil
|
304
|
+
@protocol = protocol || :udp
|
305
|
+
@s_mu = Mutex.new
|
306
|
+
connect
|
117
307
|
end
|
118
308
|
|
119
309
|
# @attribute [w] namespace
|
@@ -135,16 +325,24 @@ class Statsd
|
|
135
325
|
|
136
326
|
# @attribute [w] host
|
137
327
|
# Writes are not thread safe.
|
328
|
+
# Users should call hup after making changes.
|
138
329
|
def host=(host)
|
139
330
|
@host = host || '127.0.0.1'
|
140
331
|
end
|
141
332
|
|
142
333
|
# @attribute [w] port
|
143
334
|
# Writes are not thread safe.
|
335
|
+
# Users should call hup after making changes.
|
144
336
|
def port=(port)
|
145
337
|
@port = port || 8125
|
146
338
|
end
|
147
339
|
|
340
|
+
# @attribute [w] stat_delimiter
|
341
|
+
# Allows for custom delimiter replacement for :: when Ruby modules are transformed to statsd metric name
|
342
|
+
def delimiter=(delimiter)
|
343
|
+
@delimiter = delimiter || "."
|
344
|
+
end
|
345
|
+
|
148
346
|
# Sends an increment (count = 1) for the given stat to the statsd server.
|
149
347
|
#
|
150
348
|
# @param [String] stat stat name
|
@@ -187,6 +385,22 @@ class Statsd
|
|
187
385
|
send_stats stat, value, :g, sample_rate
|
188
386
|
end
|
189
387
|
|
388
|
+
# Sends an arbitary set value for the given stat to the statsd server.
|
389
|
+
#
|
390
|
+
# This is for recording counts of unique events, which are useful to
|
391
|
+
# see on graphs to correlate to other values. For example, a deployment
|
392
|
+
# might get recorded as a set, and be drawn as annotations on a CPU history
|
393
|
+
# graph.
|
394
|
+
#
|
395
|
+
# @param [String] stat stat name.
|
396
|
+
# @param [Numeric] value event value.
|
397
|
+
# @param [Numeric] sample_rate sample rate, 1 for always
|
398
|
+
# @example Report a deployment happening:
|
399
|
+
# $statsd.set('deployment', DEPLOYMENT_EVENT_CODE)
|
400
|
+
def set(stat, value, sample_rate=1)
|
401
|
+
send_stats stat, value, :s, sample_rate
|
402
|
+
end
|
403
|
+
|
190
404
|
# Sends a timing (in ms) for the given stat to the statsd server. The
|
191
405
|
# sample_rate determines what percentage of the time this report is sent. The
|
192
406
|
# statsd server then uses the sample_rate to correctly track the average
|
@@ -208,9 +422,10 @@ class Statsd
|
|
208
422
|
# @example Report the time (in ms) taken to activate an account
|
209
423
|
# $statsd.time('account.activate') { @account.activate! }
|
210
424
|
def time(stat, sample_rate=1)
|
211
|
-
start =
|
425
|
+
start = MonotonicTime.time_in_ms
|
212
426
|
result = yield
|
213
|
-
|
427
|
+
ensure
|
428
|
+
timing(stat, (MonotonicTime.time_in_ms - start).round, sample_rate)
|
214
429
|
result
|
215
430
|
end
|
216
431
|
|
@@ -225,14 +440,53 @@ class Statsd
|
|
225
440
|
# batch.gauge('user.count', User.count)
|
226
441
|
# end
|
227
442
|
def batch(&block)
|
228
|
-
Batch.new(self).easy
|
443
|
+
Batch.new(self).easy(&block)
|
444
|
+
end
|
445
|
+
|
446
|
+
# Reconnects the socket, useful if the address of the statsd has changed. This
|
447
|
+
# method is not thread safe from a perspective of stat submission. It is safe
|
448
|
+
# from resource leaks. Users do not normally need to call this, but calling it
|
449
|
+
# may be appropriate when reconfiguring a process (e.g. from HUP).
|
450
|
+
def connect
|
451
|
+
@s_mu.synchronize do
|
452
|
+
begin
|
453
|
+
@socket.close if @socket
|
454
|
+
rescue
|
455
|
+
# Errors are ignored on reconnects.
|
456
|
+
end
|
457
|
+
|
458
|
+
case @protocol
|
459
|
+
when :tcp
|
460
|
+
@socket = TCPSocket.new @host, @port
|
461
|
+
else
|
462
|
+
@socket = UDPSocket.new Addrinfo.ip(@host).afamily
|
463
|
+
@socket.connect host, port
|
464
|
+
end
|
465
|
+
end
|
229
466
|
end
|
230
467
|
|
231
468
|
protected
|
232
469
|
|
233
470
|
def send_to_socket(message)
|
234
471
|
self.class.logger.debug { "Statsd: #{message}" } if self.class.logger
|
235
|
-
|
472
|
+
|
473
|
+
retries = 0
|
474
|
+
n = 0
|
475
|
+
while true
|
476
|
+
# send(2) is atomic, however, in stream cases (TCP) the socket is left
|
477
|
+
# in an inconsistent state if a partial message is written. If that case
|
478
|
+
# occurs, the socket is closed down and we retry on a new socket.
|
479
|
+
message = @protocol == :tcp ? message + "\n" : message
|
480
|
+
n = socket.write(message) rescue (err = $!; 0)
|
481
|
+
if n == message.length
|
482
|
+
break
|
483
|
+
end
|
484
|
+
|
485
|
+
connect
|
486
|
+
retries += 1
|
487
|
+
raise (err || "statsd: Failed to send after #{retries} attempts") if retries >= 5
|
488
|
+
end
|
489
|
+
n
|
236
490
|
rescue => boom
|
237
491
|
self.class.logger.error { "Statsd: #{boom.class} #{boom}" } if self.class.logger
|
238
492
|
nil
|
@@ -243,13 +497,15 @@ class Statsd
|
|
243
497
|
def send_stats(stat, delta, type, sample_rate=1)
|
244
498
|
if sample_rate == 1 or rand < sample_rate
|
245
499
|
# Replace Ruby module scoping with '.' and reserved chars (: | @) with underscores.
|
246
|
-
stat = stat.to_s.gsub('::',
|
500
|
+
stat = stat.to_s.gsub('::', delimiter).tr(':|@', '_')
|
247
501
|
rate = "|@#{sample_rate}" unless sample_rate == 1
|
248
502
|
send_to_socket "#{prefix}#{stat}#{postfix}:#{delta}|#{type}#{rate}"
|
249
503
|
end
|
250
504
|
end
|
251
505
|
|
252
506
|
def socket
|
253
|
-
|
507
|
+
# Subtle: If the socket is half-way through initialization in connect, it
|
508
|
+
# cannot be used yet.
|
509
|
+
@s_mu.synchronize { @socket } || raise(ThreadError, "socket missing")
|
254
510
|
end
|
255
511
|
end
|