statsd-ruby-tcp 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.document +5 -0
- data/.github/workflows/ci.yml +34 -0
- data/.gitignore +43 -0
- data/.travis.yml +19 -0
- data/Gemfile +2 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +86 -0
- data/Rakefile +15 -0
- data/lib/statsd-ruby.rb +1 -0
- data/lib/statsd.rb +513 -0
- data/lib/statsd/monotonic_time.rb +35 -0
- data/spec/helper.rb +43 -0
- data/spec/statsd_admin_spec.rb +117 -0
- data/spec/statsd_spec.rb +593 -0
- data/statsd-ruby-tcp.gemspec +24 -0
- metadata +151 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 4ff155c12281bc745e968ebfbde358cdbc24c870c6d1c10977b1bc1bab44878b
|
4
|
+
data.tar.gz: 529543111937436fae9df8c5306e1e05d7b2fade976d6dc2642908ad552e60a3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0a66d75d540e01cb9e4217fc3f371419f2a1e17d7633cffea22153c03cf0b295d3713175e71cb8125113e33b866ae7fe74f5787ca87d4ec4a683d3574f094471
|
7
|
+
data.tar.gz: 1115aabe2841a5fbff7ca22fb2fd5a9ac4ff812a6cb630744c39f7db26661392f701a2cd424ac7c106673e3a484918aa7e9ca35313dff9450e83057efba7d88f
|
data/.document
ADDED
@@ -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/.gitignore
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# simplecov generated
|
2
|
+
coverage
|
3
|
+
|
4
|
+
# rdoc generated
|
5
|
+
rdoc
|
6
|
+
|
7
|
+
# yard generated
|
8
|
+
doc
|
9
|
+
.yardoc
|
10
|
+
|
11
|
+
# bundler
|
12
|
+
.bundle
|
13
|
+
Gemfile.lock
|
14
|
+
|
15
|
+
# jeweler generated
|
16
|
+
pkg
|
17
|
+
|
18
|
+
# Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore:
|
19
|
+
#
|
20
|
+
# * Create a file at ~/.gitignore
|
21
|
+
# * Include files you want ignored
|
22
|
+
# * Run: git config --global core.excludesfile ~/.gitignore
|
23
|
+
#
|
24
|
+
# After doing this, these files will be ignored in all your git projects,
|
25
|
+
# saving you from having to 'pollute' every project you touch with them
|
26
|
+
#
|
27
|
+
# Not sure what to needs to be ignored for particular editors/OSes? Here's some ideas to get you started. (Remember, remove the leading # of the line)
|
28
|
+
#
|
29
|
+
# For MacOS:
|
30
|
+
#
|
31
|
+
#.DS_Store
|
32
|
+
#
|
33
|
+
# For TextMate
|
34
|
+
#*.tmproj
|
35
|
+
#tmtags
|
36
|
+
#
|
37
|
+
# For emacs:
|
38
|
+
#*~
|
39
|
+
#\#*
|
40
|
+
#.\#*
|
41
|
+
#
|
42
|
+
# For vim:
|
43
|
+
#*.swp
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011, 2012, 2013 Rein Henrichs
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,86 @@
|
|
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
|
+
|
3
|
+
A Ruby client for {StatsD}[https://github.com/etsy/statsd]
|
4
|
+
|
5
|
+
= Installing
|
6
|
+
|
7
|
+
Bundler:
|
8
|
+
gem "statsd-ruby"
|
9
|
+
|
10
|
+
= Basic Usage
|
11
|
+
|
12
|
+
# Set up a global Statsd client for a server on localhost:9125
|
13
|
+
$statsd = Statsd.new 'localhost', 9125
|
14
|
+
|
15
|
+
# Set up a global Statsd client for a server on IPv6 port 9125
|
16
|
+
$statsd = Statsd.new '::1', 9125
|
17
|
+
|
18
|
+
# Send some stats
|
19
|
+
$statsd.increment 'garets'
|
20
|
+
$statsd.timing 'glork', 320
|
21
|
+
$statsd.gauge 'bork', 100
|
22
|
+
|
23
|
+
# Use {#time} to time the execution of a block
|
24
|
+
$statsd.time('account.activate') { @account.activate! }
|
25
|
+
|
26
|
+
# Create a namespaced statsd client and increment 'account.activate'
|
27
|
+
statsd = Statsd.new('localhost').tap{|sd| sd.namespace = 'account'}
|
28
|
+
statsd.increment 'activate'
|
29
|
+
|
30
|
+
= Testing
|
31
|
+
|
32
|
+
Run the specs with <tt>rake spec</tt>
|
33
|
+
|
34
|
+
= Performance
|
35
|
+
|
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]
|
41
|
+
|
42
|
+
== Contributing to statsd
|
43
|
+
|
44
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
45
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
46
|
+
* Fork the project
|
47
|
+
* Start a feature/bugfix branch
|
48
|
+
* Commit and push until you are happy with your contribution
|
49
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
50
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
51
|
+
|
52
|
+
== Contributors
|
53
|
+
|
54
|
+
* Rein Henrichs
|
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
|
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
|
79
|
+
* Ryan Tomayko
|
80
|
+
* Schuyler Erle
|
81
|
+
* Thomas Whaples
|
82
|
+
* Trae Robrock
|
83
|
+
|
84
|
+
== Copyright
|
85
|
+
|
86
|
+
Copyright (c) 2011, 2012, 2013 Rein Henrichs. See LICENSE.txt for further details.
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require 'bundler/gem_tasks'
|
3
|
+
|
4
|
+
task :default => :spec
|
5
|
+
|
6
|
+
require 'rake/testtask'
|
7
|
+
Rake::TestTask.new(:spec) do |spec|
|
8
|
+
spec.libs << 'lib' << 'spec'
|
9
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
10
|
+
spec.verbose = true
|
11
|
+
spec.warning = true
|
12
|
+
end
|
13
|
+
|
14
|
+
require 'yard'
|
15
|
+
YARD::Rake::YardocTask.new
|
data/lib/statsd-ruby.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'statsd'
|
data/lib/statsd.rb
ADDED
@@ -0,0 +1,513 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'forwardable'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
require 'statsd/monotonic_time'
|
6
|
+
|
7
|
+
# = Statsd: A Statsd client (https://github.com/etsy/statsd)
|
8
|
+
#
|
9
|
+
# @example Set up a global Statsd client for a server on localhost:8125
|
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
|
13
|
+
# @example Send some stats
|
14
|
+
# $statsd.increment 'garets'
|
15
|
+
# $statsd.timing 'glork', 320
|
16
|
+
# $statsd.gauge 'bork', 100
|
17
|
+
# @example Use {#time} to time the execution of a block
|
18
|
+
# $statsd.time('account.activate') { @account.activate! }
|
19
|
+
# @example Create a namespaced statsd client and increment 'account.activate'
|
20
|
+
# statsd = Statsd.new('localhost').tap{|sd| sd.namespace = 'account'}
|
21
|
+
# statsd.increment 'activate'
|
22
|
+
#
|
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
|
25
|
+
# mutexed, it is expected that users will not change these at runtime in
|
26
|
+
# threaded environments. If users require such use cases, it is recommend that
|
27
|
+
# users either mutex around their Statsd object, or create separate objects for
|
28
|
+
# each namespace / host+port combination.
|
29
|
+
class Statsd
|
30
|
+
|
31
|
+
# = Batch: A batching statsd proxy
|
32
|
+
#
|
33
|
+
# @example Batch a set of instruments using Batch and manual flush:
|
34
|
+
# $statsd = Statsd.new 'localhost', 8125
|
35
|
+
# batch = Statsd::Batch.new($statsd)
|
36
|
+
# batch.increment 'garets'
|
37
|
+
# batch.timing 'glork', 320
|
38
|
+
# batch.gauge 'bork', 100
|
39
|
+
# batch.flush
|
40
|
+
#
|
41
|
+
# Batch is a subclass of Statsd, but with a constructor that proxies to a
|
42
|
+
# normal Statsd instance. It has it's own batch_size and namespace parameters
|
43
|
+
# (that inherit defaults from the supplied Statsd instance). It is recommended
|
44
|
+
# that some care is taken if setting very large batch sizes. If the batch size
|
45
|
+
# exceeds the allowed packet size for UDP on your network, communication
|
46
|
+
# troubles may occur and data will be lost.
|
47
|
+
class Batch < Statsd
|
48
|
+
|
49
|
+
extend Forwardable
|
50
|
+
def_delegators :@statsd,
|
51
|
+
:namespace, :namespace=,
|
52
|
+
:host, :host=,
|
53
|
+
:port, :port=,
|
54
|
+
:prefix,
|
55
|
+
:postfix,
|
56
|
+
:delimiter, :delimiter=
|
57
|
+
|
58
|
+
attr_accessor :batch_size, :batch_byte_size, :flush_interval
|
59
|
+
|
60
|
+
# @param [Statsd] statsd requires a configured Statsd instance
|
61
|
+
def initialize(statsd)
|
62
|
+
@statsd = statsd
|
63
|
+
@batch_size = statsd.batch_size
|
64
|
+
@batch_byte_size = statsd.batch_byte_size
|
65
|
+
@flush_interval = statsd.flush_interval
|
66
|
+
@backlog = []
|
67
|
+
@backlog_bytesize = 0
|
68
|
+
@last_flush = Time.now
|
69
|
+
end
|
70
|
+
|
71
|
+
# @yield [Batch] yields itself
|
72
|
+
#
|
73
|
+
# A convenience method to ensure that data is not lost in the event of an
|
74
|
+
# exception being thrown. Batches will be transmitted on the parent socket
|
75
|
+
# as soon as the batch is full, and when the block finishes.
|
76
|
+
def easy
|
77
|
+
yield self
|
78
|
+
ensure
|
79
|
+
flush
|
80
|
+
end
|
81
|
+
|
82
|
+
def flush
|
83
|
+
unless @backlog.empty?
|
84
|
+
@statsd.send_to_socket @backlog.join("\n")
|
85
|
+
@backlog.clear
|
86
|
+
@backlog_bytesize = 0
|
87
|
+
@last_flush = Time.now
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
protected
|
92
|
+
|
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
|
101
|
+
@backlog << message
|
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)
|
108
|
+
flush
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def last_flush_seconds_ago
|
113
|
+
Time.now - @last_flush
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
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
|
258
|
+
|
259
|
+
# A namespace to prepend to all statsd calls.
|
260
|
+
attr_reader :namespace
|
261
|
+
|
262
|
+
# StatsD host. Defaults to 127.0.0.1.
|
263
|
+
attr_reader :host
|
264
|
+
|
265
|
+
# StatsD port. Defaults to 8125.
|
266
|
+
attr_reader :port
|
267
|
+
|
268
|
+
# StatsD namespace prefix, generated from #namespace
|
269
|
+
attr_reader :prefix
|
270
|
+
|
271
|
+
# The default batch size for new batches. Set to nil to use batch_byte_size (default: 10)
|
272
|
+
attr_accessor :batch_size
|
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
|
+
|
280
|
+
# a postfix to append to all metrics
|
281
|
+
attr_reader :postfix
|
282
|
+
|
283
|
+
# The replacement of :: on ruby module names when transformed to statsd metric names
|
284
|
+
attr_reader :delimiter
|
285
|
+
|
286
|
+
class << self
|
287
|
+
# Set to a standard logger instance to enable debug logging.
|
288
|
+
attr_accessor :logger
|
289
|
+
end
|
290
|
+
|
291
|
+
# @param [String] host your statsd host
|
292
|
+
# @param [Integer] port your statsd port
|
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 = "."
|
298
|
+
@prefix = nil
|
299
|
+
@batch_size = 10
|
300
|
+
@batch_byte_size = nil
|
301
|
+
@flush_interval = nil
|
302
|
+
@postfix = nil
|
303
|
+
@socket = nil
|
304
|
+
@protocol = protocol || :udp
|
305
|
+
@s_mu = Mutex.new
|
306
|
+
connect
|
307
|
+
end
|
308
|
+
|
309
|
+
# @attribute [w] namespace
|
310
|
+
# Writes are not thread safe.
|
311
|
+
def namespace=(namespace)
|
312
|
+
@namespace = namespace
|
313
|
+
@prefix = "#{namespace}."
|
314
|
+
end
|
315
|
+
|
316
|
+
# @attribute [w] postfix
|
317
|
+
# A value to be appended to the stat name after a '.'. If the value is
|
318
|
+
# blank then the postfix will be reset to nil (rather than to '.').
|
319
|
+
def postfix=(pf)
|
320
|
+
case pf
|
321
|
+
when nil, false, '' then @postfix = nil
|
322
|
+
else @postfix = ".#{pf}"
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
# @attribute [w] host
|
327
|
+
# Writes are not thread safe.
|
328
|
+
# Users should call hup after making changes.
|
329
|
+
def host=(host)
|
330
|
+
@host = host || '127.0.0.1'
|
331
|
+
end
|
332
|
+
|
333
|
+
# @attribute [w] port
|
334
|
+
# Writes are not thread safe.
|
335
|
+
# Users should call hup after making changes.
|
336
|
+
def port=(port)
|
337
|
+
@port = port || 8125
|
338
|
+
end
|
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
|
+
|
346
|
+
# Sends an increment (count = 1) for the given stat to the statsd server.
|
347
|
+
#
|
348
|
+
# @param [String] stat stat name
|
349
|
+
# @param [Numeric] sample_rate sample rate, 1 for always
|
350
|
+
# @see #count
|
351
|
+
def increment(stat, sample_rate=1)
|
352
|
+
count stat, 1, sample_rate
|
353
|
+
end
|
354
|
+
|
355
|
+
# Sends a decrement (count = -1) for the given stat to the statsd server.
|
356
|
+
#
|
357
|
+
# @param [String] stat stat name
|
358
|
+
# @param [Numeric] sample_rate sample rate, 1 for always
|
359
|
+
# @see #count
|
360
|
+
def decrement(stat, sample_rate=1)
|
361
|
+
count stat, -1, sample_rate
|
362
|
+
end
|
363
|
+
|
364
|
+
# Sends an arbitrary count for the given stat to the statsd server.
|
365
|
+
#
|
366
|
+
# @param [String] stat stat name
|
367
|
+
# @param [Integer] count count
|
368
|
+
# @param [Numeric] sample_rate sample rate, 1 for always
|
369
|
+
def count(stat, count, sample_rate=1)
|
370
|
+
send_stats stat, count, :c, sample_rate
|
371
|
+
end
|
372
|
+
|
373
|
+
# Sends an arbitary gauge value for the given stat to the statsd server.
|
374
|
+
#
|
375
|
+
# This is useful for recording things like available disk space,
|
376
|
+
# memory usage, and the like, which have different semantics than
|
377
|
+
# counters.
|
378
|
+
#
|
379
|
+
# @param [String] stat stat name.
|
380
|
+
# @param [Numeric] value gauge value.
|
381
|
+
# @param [Numeric] sample_rate sample rate, 1 for always
|
382
|
+
# @example Report the current user count:
|
383
|
+
# $statsd.gauge('user.count', User.count)
|
384
|
+
def gauge(stat, value, sample_rate=1)
|
385
|
+
send_stats stat, value, :g, sample_rate
|
386
|
+
end
|
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
|
+
|
404
|
+
# Sends a timing (in ms) for the given stat to the statsd server. The
|
405
|
+
# sample_rate determines what percentage of the time this report is sent. The
|
406
|
+
# statsd server then uses the sample_rate to correctly track the average
|
407
|
+
# timing for the stat.
|
408
|
+
#
|
409
|
+
# @param [String] stat stat name
|
410
|
+
# @param [Integer] ms timing in milliseconds
|
411
|
+
# @param [Numeric] sample_rate sample rate, 1 for always
|
412
|
+
def timing(stat, ms, sample_rate=1)
|
413
|
+
send_stats stat, ms, :ms, sample_rate
|
414
|
+
end
|
415
|
+
|
416
|
+
# Reports execution time of the provided block using {#timing}.
|
417
|
+
#
|
418
|
+
# @param [String] stat stat name
|
419
|
+
# @param [Numeric] sample_rate sample rate, 1 for always
|
420
|
+
# @yield The operation to be timed
|
421
|
+
# @see #timing
|
422
|
+
# @example Report the time (in ms) taken to activate an account
|
423
|
+
# $statsd.time('account.activate') { @account.activate! }
|
424
|
+
def time(stat, sample_rate=1)
|
425
|
+
start = MonotonicTime.time_in_ms
|
426
|
+
result = yield
|
427
|
+
ensure
|
428
|
+
timing(stat, (MonotonicTime.time_in_ms - start).round, sample_rate)
|
429
|
+
result
|
430
|
+
end
|
431
|
+
|
432
|
+
# Creates and yields a Batch that can be used to batch instrument reports into
|
433
|
+
# larger packets. Batches are sent either when the packet is "full" (defined
|
434
|
+
# by batch_size), or when the block completes, whichever is the sooner.
|
435
|
+
#
|
436
|
+
# @yield [Batch] a statsd subclass that collects and batches instruments
|
437
|
+
# @example Batch two instument operations:
|
438
|
+
# $statsd.batch do |batch|
|
439
|
+
# batch.increment 'sys.requests'
|
440
|
+
# batch.gauge('user.count', User.count)
|
441
|
+
# end
|
442
|
+
def batch(&block)
|
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
|
466
|
+
end
|
467
|
+
|
468
|
+
protected
|
469
|
+
|
470
|
+
def send_to_socket(message)
|
471
|
+
self.class.logger.debug { "Statsd: #{message}" } if self.class.logger
|
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
|
+
l = @protocol == :tcp ? message.length + 1 : message.length
|
480
|
+
br = @protocol == :tcp ? "\n" : ""
|
481
|
+
n = socket.write(message + br)
|
482
|
+
|
483
|
+
if n == l
|
484
|
+
break
|
485
|
+
end
|
486
|
+
|
487
|
+
connect
|
488
|
+
retries += 1
|
489
|
+
raise "statsd: Failed to send after #{retries} attempts" if retries >= 5
|
490
|
+
end
|
491
|
+
n
|
492
|
+
rescue => boom
|
493
|
+
self.class.logger.error { "Statsd: #{boom.class} #{boom}" } if self.class.logger
|
494
|
+
nil
|
495
|
+
end
|
496
|
+
|
497
|
+
private
|
498
|
+
|
499
|
+
def send_stats(stat, delta, type, sample_rate=1)
|
500
|
+
if sample_rate == 1 or rand < sample_rate
|
501
|
+
# Replace Ruby module scoping with '.' and reserved chars (: | @) with underscores.
|
502
|
+
stat = stat.to_s.gsub('::', delimiter).tr(':|@', '_')
|
503
|
+
rate = "|@#{sample_rate}" unless sample_rate == 1
|
504
|
+
send_to_socket "#{prefix}#{stat}#{postfix}:#{delta}|#{type}#{rate}"
|
505
|
+
end
|
506
|
+
end
|
507
|
+
|
508
|
+
def socket
|
509
|
+
# Subtle: If the socket is half-way through initialization in connect, it
|
510
|
+
# cannot be used yet.
|
511
|
+
@s_mu.synchronize { @socket } || raise(ThreadError, "socket missing")
|
512
|
+
end
|
513
|
+
end
|