statsd-instrument 1.7.2 → 2.0.0beta

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d50bba4fad7c7d60e2272ec23888004b0cbebbd1
4
+ data.tar.gz: b36122aa5c275370c1a0e3e9edd3803321a4a960
5
+ SHA512:
6
+ metadata.gz: 0e060d63fb216442bd387a131adca06326560aec60813efe34a608182b5066e698fdf217f13bee3ffbdcd8489e105079b0e99aca507729627a73014b5e2749e6
7
+ data.tar.gz: 39f3c512927e0c8ac41ff7c21936853d1d7e048b02ed3030c2c21bd67e3f07f703d6093fa54062617e102b9b273a809c23c218675b1a6280b349a46f71630856
data/.travis.yml CHANGED
@@ -1,7 +1,9 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 1.8.7
4
- - ree
5
3
  - 1.9.3
6
4
  - 2.0.0
7
- - 2.1.0
5
+ - 2.1.1
6
+ - ruby-head
7
+ matrix:
8
+ allow_failures:
9
+ - rvm: ruby-head
data/Gemfile CHANGED
@@ -1,5 +1,2 @@
1
- source "http://rubygems.org"
1
+ source "https://rubygems.org"
2
2
  gemspec
3
-
4
- gem 'SystemTimer', :require => nil, :platform => :mri_18
5
- gem 'rake', '~> 10.0.0' if RUBY_VERSION.to_f < 1.9
data/README.md CHANGED
@@ -4,9 +4,9 @@
4
4
 
5
5
  This is a ruby client for statsd (http://github.com/etsy/statsd). It provides a lightweight way to track and measure metrics in your application.
6
6
 
7
- We call out to statsd by sending data over a UDP socket. UDP sockets are fast, but unreliable, there is no guarantee that your data will ever arrive at it's location. In other words, fire and forget. This is perfect for this use case because it means your code doesn't get bogged down trying to log statistics. We send data to statsd several times per request and haven't noticed a performance hit.
7
+ We call out to statsd by sending data over a UDP socket. UDP sockets are fast, but unreliable, there is no guarantee that your data will ever arrive at its location. In other words, fire and forget. This is perfect for this use case because it means your code doesn't get bogged down trying to log statistics. We send data to statsd several times per request and haven't noticed a performance hit.
8
8
 
9
- The fact that all of your stats data may not make it into statsd is no issue. Graphite (the graph database that statsd is built on) will only show you trends in your data. Internally it only keeps enough data to satisfy the levels of granularity we specify. As well as satisfying it's requirement as a fixed size database. We can throw as much data at it as we want it and it will do it's best to show us the trends over time and get rid of the fluff.
9
+ The fact that all of your stats data may not make it into statsd is no issue. Graphite (the graph database that statsd is built on) will only show you trends in your data. Internally it only keeps enough data to satisfy the levels of granularity we specify. As well as satisfying its requirement as a fixed size database. We can throw as much data at it as we want it and it will do its best to show us the trends over time and get rid of the fluff.
10
10
 
11
11
  For Shopify, our retention periods are:
12
12
 
@@ -18,39 +18,48 @@ This is the same as what Etsy uses (mentioned in the README for http://github.co
18
18
 
19
19
  ## Configuration
20
20
 
21
- ``` ruby
22
- # The UDP endpoint to which you want to submit your metrics.
23
- # This is set to the environment variable STATSD_ADDR if it is set.
24
- StatsD.server = 'statsd.myservice.com:8125'
21
+ The library comes with different backends. Based on your environment (detected using environment
22
+ variables), it will select one of the following backends by default:
25
23
 
26
- # Events are only actually submitted in production mode. For any other value, thewy are logged instead
27
- # This value is set by to the value of the RAILS_ENV or RACK_ENV environment variable if it is set.
28
- StatsD.mode = :production
24
+ - **Production** environment: `StatsD::Instrument::Backend::UDPBackend` will actually send UDP packets.
25
+ It will configure itself using environment variables: it uses `STATSD_ADDR` for the address to connect
26
+ to (default `"localhost:8125"`), and `STATSD_IMPLEMENTATION` to set the protocol variant. (See below)
27
+ - **Test** environment: `StatsD::Instrument::Backend::NullBackend` will swallow all calls. See below for
28
+ notes on writing tests.
29
+ - **Development**, and all other, environments: `StatsD::Instrument::Backend::LoggerBackend` will log all
30
+ calls to stdout.
29
31
 
30
- # Logger to which commands are logged when not in :production mode.
31
- # In production only errors are logged to the console.
32
- StatsD.logger = Rails.logger
32
+ You can override the currently active backend by setting `StatsD.backend`:
33
33
 
34
- # An optional prefix to be added to each stat.
35
- StatsD.prefix = 'my_app'
34
+ ``` ruby
35
+ # Sets up a UDP backend. First argument is the UDP address to send StatsD packets to,
36
+ # second argument specifies the protocol variant (i.e. `:statsd`, `:statsite`, or `:datadog`).
37
+ StatsD.backend = StatsD::Instrument::Backend::UDPBackend.new("1.2.3.4:8125", :statsite)
36
38
 
37
- # Sample 10% of events. By default all events are reported, which may overload your network or server.
38
- # You can, and should vary this on a per metric basis, depending on frequency and accuracy requirements
39
- StatsD.default_sample_rate = 0.1
39
+ # Sets up a logger backend
40
+ StatsD.backend = StatsD::Instrument::Backend::LoggerBackend.new(Rails.logger)
41
+ ```
40
42
 
43
+ The other available settings, with their default, are
41
44
 
42
- ```
45
+ ``` ruby
46
+ # Logger to which commands are logged when using the LoggerBackend, which is
47
+ # the default in development environment. Also, any errors or warnings will
48
+ # be logged here.
49
+ StatsD.logger = defiend?(Rails) : Rails.logger ? Logger.new($stderr)
43
50
 
44
- There are several implementations of StatsD out there, all with slight protocol variations. You can this library to use the proper protocol by informing it about what implementation you use. By default, it will use the `STATSD_IMPLEMENTATION` environment variable, if it is not set it will use the protocol of the original Etsy implementation.
51
+ # An optional prefix to be added to each metric.
52
+ StatsD.prefix = nil # but can be set to any string
45
53
 
46
- ```
47
- StatsD.implementation = :datadog # Enable datadog extensions: tags and histograms
48
- StatsD.implementation = :statsite # Enable keyvalue-style gauges
54
+ # Sample 10% of events. By default all events are reported, which may overload your network or server.
55
+ # You can, and should vary this on a per metric basis, depending on frequency and accuracy requirements
56
+ StatsD.default_sample_rate = (ENV['STATSD_SAMPLE_RATE'] || 0.1 ).to_f
49
57
  ```
50
58
 
51
59
  ## StatsD keys
52
60
 
53
- StatsD keys look like 'admin.logins.api.success'. Each dot in the key represents a 'folder' in the graphite interface. You can include any data you want in the keys.
61
+ StatsD keys look like 'admin.logins.api.success'. Dots are used as namespace separators.
62
+ In Graphite, they will show up as folders.
54
63
 
55
64
  ## Usage
56
65
 
@@ -185,7 +194,7 @@ the object the function is being called on and the array of arguments
185
194
  passed.
186
195
 
187
196
  ```ruby
188
- GoogleBase.statsd_count :insert, lamdba{|object, args| object.class.to_s.downcase + "." + args.first.to_s + ".insert" }
197
+ GoogleBase.statsd_count :insert, lambda{|object, args| object.class.to_s.downcase + "." + args.first.to_s + ".insert" }
189
198
  ```
190
199
 
191
200
  ### Tags
@@ -197,9 +206,70 @@ StatsD.increment('my.counter', tags: ['env:production', 'unicorn'])
197
206
  GoogleBase.statsd_count :insert, 'GoogleBase.insert', tags: ['env:production']
198
207
  ```
199
208
 
209
+ If implementation is not set to `:datadog`, tags will not be included in the UDP packets, and a
210
+ warning is logged to `StatsD.logger`.
211
+
212
+ ## Testing
213
+
214
+ This library come swith a module called `StatsD::Instrument::Assertions` to help you write tests
215
+ to verify StatsD is called properly.
216
+
217
+ ``` ruby
218
+ class MyTestcase < Minitest::Test
219
+ include StatsD::Instrument::Assertions
220
+
221
+ def test_some_metrics
222
+ # This will pass if there is exactly one matching StatsD call
223
+ # it will ignore any other, non matching calls.
224
+ assert_statsd_increment('counter.name', sample_rate: 1.0) do
225
+ StatsD.increment('unrelated') # doesn't match
226
+ StatsD.increment('counter.name', sample_rate: 1.0) # matches
227
+ StatsD.increment('counter.name', sample_rate: 0.1) # doesn't match
228
+ end
229
+
230
+ # Set `times` if there will be multiple matches:
231
+ assert_statsd_increment('counter.name', times: 2) do
232
+ StatsD.increment('unrelated') # doesn't match
233
+ StatsD.increment('counter.name', sample_rate: 1.0) # matches
234
+ StatsD.increment('counter.name', sample_rate: 0.1) # matches too
235
+ end
236
+ end
237
+
238
+ def test_no_udp_traffic
239
+ # Verifies no StatsD calls occured at all.
240
+ assert_no_statsd_calls do
241
+ do_some_work
242
+ end
243
+
244
+ # Verifies no StatsD calls occured for the given metric.
245
+ assert_no_statsd_calls('metric_name') do
246
+ do_some_work
247
+ end
248
+ end
249
+
250
+ def test_more_complicated_stuff
251
+ # capture_statsd_calls will capture all the StatsD calls in the
252
+ # given block, and returns them as an array. You can then run your
253
+ # own assertions on it.
254
+ metrics = capture_statsd_calls do
255
+ StatsD.increment('mycounter', sample_rate: 0.01)
256
+ end
257
+
258
+ assert_equal 1, metrics.length
259
+ assert_equal 'mycounter', metrics[0].name
260
+ assert_equal :c, metrics[0].type
261
+ assert_equal 1, metrics[0].value
262
+ assert_equal 0.01, metrics[0].sample_rate
263
+ end
264
+ end
265
+
266
+ ```
267
+
200
268
  ## Reliance on DNS
201
269
 
202
- Out of the box StatsD is set up to be unidirectional fire-and-forget over UDP. Configuring the StatsD host to be a non-ip will trigger a DNS lookup (ie synchronous round trip network call) for each metric sent. This can be particularly problematic in clouds that have a shared DNS infrastructure such as AWS.
270
+ Out of the box StatsD is set up to be unidirectional fire-and-forget over UDP. Configuring
271
+ the StatsD host to be a non-ip will trigger a DNS lookup (i.e. a synchronous TCP round trip).
272
+ This can be particularly problematic in clouds that have a shared DNS infrastructure such as AWS.
203
273
 
204
274
  ### Common Workarounds
205
275
 
@@ -211,11 +281,9 @@ Out of the box StatsD is set up to be unidirectional fire-and-forget over UDP. C
211
281
 
212
282
  Tested on several Ruby versions using Travis CI:
213
283
 
214
- * Ruby 1.8.7
215
- * Ruby Enterprise Edition 1.8.7
216
284
  * Ruby 1.9.3
217
285
  * Ruby 2.0.0
218
- * Ruby 2.1.0
286
+ * Ruby 2.1.1
219
287
 
220
288
  ## Contributing
221
289
 
@@ -224,5 +292,5 @@ This project is MIT licensed and welcomes outside contributions.
224
292
  1. Fork the repository, and create a feature branch.
225
293
  2. Implement the feature, and add tests that cover the new changes functionality.
226
294
  3. Update the README.
227
- 4. Create a pull request. Make sure that you get a CI pass on it.
295
+ 4. Create a pull request. Make sure that you get a Travis CI pass on it.
228
296
  5. Ping @jstorimer and/or @wvanbergen for review.
@@ -0,0 +1,46 @@
1
+ module StatsD::Instrument::Assertions
2
+
3
+ def capture_statsd_calls(&block)
4
+ mock_backend = StatsD::Instrument::Backends::CaptureBackend.new
5
+ old_backend, StatsD.backend = StatsD.backend, mock_backend
6
+ block.call
7
+ mock_backend.collected_metrics
8
+ ensure
9
+ StatsD.backend = old_backend
10
+ end
11
+
12
+ def assert_no_statsd_calls(metric_name = nil, &block)
13
+ metrics = capture_statsd_calls(&block)
14
+ metrics.select! { |m| m.name == metric_name } if metric_name
15
+ assert metrics.empty?, "No StatsD calls for metric #{metric_name} expected."
16
+ end
17
+
18
+ def assert_statsd_increment(metric_name, options = {}, &block)
19
+ assert_statsd_call(:c, metric_name, options, &block)
20
+ end
21
+
22
+ def assert_statsd_measure(metric_name, options = {}, &block)
23
+ assert_statsd_call(:ms, metric_name, options, &block)
24
+ end
25
+
26
+ def assert_statsd_gauge(metric_name, options = {}, &block)
27
+ assert_statsd_call(:g, metric_name, options, &block)
28
+ end
29
+
30
+ private
31
+
32
+ def assert_statsd_call(metric_type, metric_name, options = {}, &block)
33
+ options[:times] ||= 1
34
+ metrics = capture_statsd_calls(&block)
35
+ metrics = metrics.select { |m| m.type == metric_type && m.name == metric_name }
36
+ assert metrics.length > 0, "No StatsD calls for metric #{metric_name} were made."
37
+ assert options[:times] === metrics.length, "The amount of StatsD calls for metric #{metric_name} was unexpected"
38
+ metric = metrics.first
39
+
40
+ assert_equal options[:sample_rate], metric.sample_rate, "Unexpected value submitted for StatsD metric #{metric_name}" if options[:sample_rate]
41
+ assert_equal options[:value], metric.value, "Unexpected StatsD sample rate for metric #{metric_name}" if options[:value]
42
+ assert_equal Set.new(options[:tags]), Set.new(metric.tags), "Unexpected StatsD tags for metric #{metric_name}" if options[:tags]
43
+
44
+ metric
45
+ end
46
+ end
@@ -0,0 +1,10 @@
1
+ class StatsD::Instrument::Backend
2
+ def collect_metric(metric)
3
+ raise NotImplementedError, "Use a concerete backend implementation"
4
+ end
5
+ end
6
+
7
+ require 'statsd/instrument/backends/logger_backend'
8
+ require 'statsd/instrument/backends/null_backend'
9
+ require 'statsd/instrument/backends/capture_backend'
10
+ require 'statsd/instrument/backends/udp_backend'
@@ -0,0 +1,17 @@
1
+ module StatsD::Instrument::Backends
2
+ class CaptureBackend < StatsD::Instrument::Backend
3
+ attr_reader :collected_metrics
4
+
5
+ def initialize
6
+ reset
7
+ end
8
+
9
+ def collect_metric(metric)
10
+ @collected_metrics << metric
11
+ end
12
+
13
+ def reset
14
+ @collected_metrics = []
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,14 @@
1
+ module StatsD::Instrument::Backends
2
+ class LoggerBackend < StatsD::Instrument::Backend
3
+
4
+ attr_accessor :logger
5
+
6
+ def initialize(logger)
7
+ @logger = logger
8
+ end
9
+
10
+ def collect_metric(metric)
11
+ logger.info "[StatsD] #{metric}"
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,6 @@
1
+ module StatsD::Instrument::Backends
2
+ class NullBackend < StatsD::Instrument::Backend
3
+ def collect_metric(metric)
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,88 @@
1
+ module StatsD::Instrument::Backends
2
+ class UDPBackend < StatsD::Instrument::Backend
3
+
4
+ DEFAULT_IMPLEMENTATION = :statsd
5
+
6
+ attr_reader :host, :port
7
+ attr_accessor :implementation
8
+
9
+ def initialize(server = nil, implementation = nil)
10
+ self.server = server || "localhost:8125"
11
+ self.implementation = (implementation || DEFAULT_IMPLEMENTATION).to_sym
12
+ end
13
+
14
+ def collect_metric(metric)
15
+ unless implementation_supports_metric_type?(metric.type)
16
+ StatsD.logger.warn("[StatsD] Metric type #{metric.type.inspect} not supported on #{implementation} implementation.")
17
+ return false
18
+ end
19
+
20
+ if metric.sample_rate < 1.0 && rand > metric.sample_rate
21
+ return false
22
+ end
23
+
24
+ write_packet(generate_packet(metric))
25
+ end
26
+
27
+ def implementation_supports_metric_type?(type)
28
+ case type
29
+ when :h; implementation == :datadog
30
+ when :kv; implementation == :statsite
31
+ else true
32
+ end
33
+ end
34
+
35
+ def server=(connection_string)
36
+ self.host, port = connection_string.split(':', 2)
37
+ self.port = port.to_i
38
+ invalidate_socket
39
+ end
40
+
41
+ def host=(host)
42
+ @host = host
43
+ invalidate_socket
44
+ end
45
+
46
+ def port=(port)
47
+ @port = port
48
+ invalidate_socket
49
+ end
50
+
51
+ def socket
52
+ if @socket.nil?
53
+ @socket = UDPSocket.new
54
+ @socket.connect(host, port)
55
+ end
56
+ @socket
57
+ end
58
+
59
+ def generate_packet(metric)
60
+ command = "#{metric.name}:#{metric.value}|#{metric.type}"
61
+ command << "|@#{metric.sample_rate}" if metric.sample_rate < 1 || (implementation == :statsite && metric.sample_rate > 1)
62
+ if metric.tags
63
+ if tags_supported?
64
+ command << "|##{metric.tags.join(',')}"
65
+ else
66
+ StatsD.logger.warn("[StatsD] Tags are only supported on Datadog implementation.")
67
+ end
68
+ end
69
+
70
+ command << "\n" if implementation == :statsite
71
+ command
72
+ end
73
+
74
+ def tags_supported?
75
+ implementation == :datadog
76
+ end
77
+
78
+ def write_packet(command)
79
+ socket.send(command, 0) > 0
80
+ rescue SocketError, IOError, SystemCallError => e
81
+ StatsD.logger.error "[StatsD] #{e.class.name}: #{e.message}"
82
+ end
83
+
84
+ def invalidate_socket
85
+ @socket = nil
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,27 @@
1
+ require 'logger'
2
+
3
+ module StatsD::Instrument::Environment
4
+ extend self
5
+
6
+ def default_backend
7
+ case environment
8
+ when 'production'
9
+ StatsD::Instrument::Backends::UDPBackend.new(ENV['STATSD_ADDR'], ENV['STATSD_IMPLEMENTATION'])
10
+ when 'test'
11
+ StatsD::Instrument::Backends::NullBackend.new
12
+ else
13
+ StatsD::Instrument::Backends::LoggerBackend.new(StatsD.logger)
14
+ end
15
+ end
16
+
17
+ def environment
18
+ if defined?(Rails)
19
+ Rails.env.to_s
20
+ else
21
+ ENV['RAILS_ENV'] || ENV['RACK_ENV'] || ENV['ENV'] || 'development'
22
+ end
23
+ end
24
+ end
25
+
26
+ StatsD.default_sample_rate = ENV.fetch('STATSD_SAMPLE_RATE', 1.0).to_f
27
+ StatsD.logger = defined?(Rails) ? Rails.logger : Logger.new($stderr)
@@ -0,0 +1,50 @@
1
+ class StatsD::Instrument::Metric
2
+
3
+ attr_accessor :type, :name, :value, :sample_rate, :tags
4
+
5
+ def initialize(options = {})
6
+ @type = options[:type] or raise ArgumentError, "Metric :type is required."
7
+ @name = options[:name] or raise ArgumentError, "Metric :name is required."
8
+ @name = StatsD.prefix ? "#{StatsD.prefix}.#{@name}" : @name unless options[:no_prefix]
9
+
10
+ @value = options[:value] || default_value
11
+ @sample_rate = options[:sample_rate] || StatsD.default_sample_rate
12
+ @tags = StatsD::Instrument::Metric.normalize_tags(options[:tags])
13
+ end
14
+
15
+ def default_value
16
+ case type
17
+ when :c; 1
18
+ else raise ArgumentError, "A value is required for metric type #{type.inspect}."
19
+ end
20
+ end
21
+
22
+ def to_s
23
+ str = "#{TYPES[type]} #{name}:#{value}"
24
+ str << " @#{sample_rate}" if sample_rate != 1.0
25
+ str << " " << tags.map { |t| "##{t}"}.join(' ') if tags
26
+ str
27
+ end
28
+
29
+ def inspect
30
+ "#<StatsD::Instrument::Metric #{self.to_s}>"
31
+ end
32
+
33
+ TYPES = {
34
+ c: 'increment',
35
+ ms: 'measure',
36
+ g: 'gauge',
37
+ h: 'histogram',
38
+ kv: 'key/value',
39
+ s: 'set',
40
+ }
41
+
42
+ def self.normalize_tags(tags)
43
+ return if tags.nil?
44
+ tags = tags.map { |k, v| "#{k}:#{v}" } if tags.is_a?(Hash)
45
+ tags.map do |tag|
46
+ components = tag.split(':', 2)
47
+ components.map { |c| c.gsub(/[^\w\.-]+/, '_') }.join(':')
48
+ end
49
+ end
50
+ end
@@ -1,5 +1,5 @@
1
1
  module StatsD
2
2
  module Instrument
3
- VERSION = "1.7.2"
3
+ VERSION = "2.0.0beta"
4
4
  end
5
5
  end
@@ -2,8 +2,6 @@ require 'socket'
2
2
  require 'benchmark'
3
3
  require 'logger'
4
4
 
5
- require 'statsd/instrument/version'
6
-
7
5
  module StatsD
8
6
  module Instrument
9
7
 
@@ -110,41 +108,23 @@ module StatsD
110
108
  end
111
109
 
112
110
  class << self
113
- attr_accessor :host, :port, :mode, :logger, :enabled, :default_sample_rate, :prefix, :implementation
114
-
115
- def server=(conn)
116
- self.host, port = conn.split(':')
117
- self.port = port.to_i
118
- invalidate_socket
119
- end
111
+ attr_accessor :logger, :default_sample_rate, :prefix
112
+ attr_writer :backend
120
113
 
121
- def host=(host)
122
- @host = host
123
- invalidate_socket
124
- end
125
-
126
- def port=(port)
127
- @port = port
128
- invalidate_socket
129
- end
130
-
131
- def invalidate_socket
132
- @socket = nil
114
+ def backend
115
+ @backend ||= StatsD::Instrument::Environment.default_backend
133
116
  end
134
117
 
135
118
  # glork:320|ms
136
- def measure(key, value = nil, *metric_options)
119
+ def measure(key, value = nil, *metric_options, &block)
137
120
  if value.is_a?(Hash) && metric_options.empty?
138
121
  metric_options = [value]
139
122
  value = nil
140
123
  end
141
124
 
142
125
  result = nil
143
- ms = value || 1000 * Benchmark.realtime do
144
- result = yield
145
- end
146
-
147
- collect(:ms, key, ms, hash_argument(metric_options))
126
+ ms = value || 1000 * Benchmark.realtime { result = block.call }
127
+ collect_metric(hash_argument(metric_options).merge(type: :ms, name: key, value: ms))
148
128
  result
149
129
  end
150
130
 
@@ -155,29 +135,27 @@ module StatsD
155
135
  value = 1
156
136
  end
157
137
 
158
- collect(:c, key, value, hash_argument(metric_options))
138
+ collect_metric(hash_argument(metric_options).merge(type: :c, name: key, value: value))
159
139
  end
160
140
 
161
141
  # gaugor:333|g
162
142
  # guagor:1234|kv|@1339864935 (statsite)
163
143
  def gauge(key, value, *metric_options)
164
- collect(:g, key, value, hash_argument(metric_options))
144
+ collect_metric(hash_argument(metric_options).merge(type: :g, name: key, value: value))
165
145
  end
166
146
 
167
147
  # histogram:123.45|h
168
148
  def histogram(key, value, *metric_options)
169
- raise NotImplementedError, "StatsD.histogram only supported on :datadog implementation." unless self.implementation == :datadog
170
- collect(:h, key, value, hash_argument(metric_options))
149
+ collect_metric(hash_argument(metric_options).merge(type: :h, name: key, value: value))
171
150
  end
172
151
 
173
152
  def key_value(key, value, *metric_options)
174
- raise NotImplementedError, "StatsD.key_value only supported on :statsite implementation." unless self.implementation == :statsite
175
- collect(:kv, key, value, hash_argument(metric_options))
153
+ collect_metric(hash_argument(metric_options).merge(type: :kv, name: key, value: value))
176
154
  end
177
155
 
178
156
  # uniques:765|s
179
157
  def set(key, value, *metric_options)
180
- collect(:s, key, value, hash_argument(metric_options))
158
+ collect_metric(hash_argument(metric_options).merge(type: :s, name: key, value: value))
181
159
  end
182
160
 
183
161
  private
@@ -195,59 +173,14 @@ module StatsD
195
173
  return hash
196
174
  end
197
175
 
198
- def socket
199
- if @socket.nil?
200
- @socket = UDPSocket.new
201
- @socket.connect(host, port)
202
- end
203
- @socket
204
- end
205
-
206
- def collect(type, k, v, options = {})
207
- return unless enabled
208
- sample_rate = options[:sample_rate] || StatsD.default_sample_rate
209
- return if sample_rate < 1 && rand > sample_rate
210
-
211
- packet = generate_packet(type, k, v, sample_rate, options[:tags])
212
- write_packet(packet)
213
- end
214
-
215
- def write_packet(command)
216
- if mode.to_s == 'production'
217
- socket.send(command, 0)
218
- else
219
- logger.info "[StatsD] #{command}"
220
- end
221
- rescue SocketError, IOError, SystemCallError => e
222
- logger.error e
223
- end
224
-
225
- def clean_tags(tags)
226
- tags = tags.map { |k, v| "#{k}:#{v}" } if tags.is_a?(Hash)
227
- tags.map do |tag|
228
- components = tag.split(':', 2)
229
- components.map { |c| c.gsub(/[^\w\.-]+/, '_') }.join(':')
230
- end
231
- end
232
-
233
- def generate_packet(type, k, v, sample_rate = default_sample_rate, tags = nil)
234
- command = self.prefix ? self.prefix + '.' : ''
235
- command << "#{k}:#{v}|#{type}"
236
- command << "|@#{sample_rate}" if sample_rate < 1 || (self.implementation == :statsite && sample_rate > 1)
237
- if tags
238
- raise ArgumentError, "Tags are only supported on :datadog implementation" unless self.implementation == :datadog
239
- command << "|##{clean_tags(tags).join(',')}"
240
- end
241
-
242
- command << "\n" if self.implementation == :statsite
243
- command
176
+ def collect_metric(options)
177
+ backend.collect_metric(StatsD::Instrument::Metric.new(options))
244
178
  end
245
179
  end
246
180
  end
247
181
 
248
- StatsD.enabled = true
249
- StatsD.default_sample_rate = 1.0
250
- StatsD.implementation = ENV.fetch('STATSD_IMPLEMENTATION', 'statsd').to_sym
251
- StatsD.server = ENV['STATSD_ADDR'] if ENV.has_key?('STATSD_ADDR')
252
- StatsD.mode = ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
253
- StatsD.logger = Logger.new($stderr)
182
+ require 'statsd/instrument/metric'
183
+ require 'statsd/instrument/backend'
184
+ require 'statsd/instrument/assertions'
185
+ require 'statsd/instrument/environment'
186
+ require 'statsd/instrument/version'
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
11
11
  spec.homepage = "https://github.com/Shopify/statsd-instrument"
12
12
  spec.summary = %q{A StatsD client for Ruby apps}
13
13
  spec.description = %q{A StatsD client for Ruby appspec. Provides metaprogramming methods to inject StatsD instrumentation into your code.}
14
- spec.license = "MIT"
14
+ spec.license = "MIT"
15
15
 
16
16
  spec.files = `git ls-files`.split($/)
17
17
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
@@ -19,5 +19,6 @@ Gem::Specification.new do |spec|
19
19
  spec.require_paths = ["lib"]
20
20
 
21
21
  spec.add_development_dependency 'rake'
22
+ spec.add_development_dependency 'minitest'
22
23
  spec.add_development_dependency 'mocha'
23
24
  end