Permalink
Browse files

Merge pull request #34 from Shopify/new-hash-syntax

New hash syntax
  • Loading branch information...
2 parents 9c061be + cc130c5 commit ff47432444c7b82a202d0b7c9c4413befe24fb6a @wvanbergen wvanbergen committed Feb 7, 2014
Showing with 111 additions and 50 deletions.
  1. +12 −4 README.md
  2. +48 −24 lib/statsd/instrument.rb
  3. +23 −13 test/statsd_instrumentation_test.rb
  4. +28 −9 test/statsd_test.rb
View
@@ -81,15 +81,15 @@ StatsD.increment('GoogleBase.insert')
StatsD.increment('GoogleBase.insert', 10)
# you can also specify a sample rate, so only 1/10 of events
# actually get to statsd. Useful for very high volume data
-StatsD.increment('GoogleBase.insert', 1, 0.1)
+StatsD.increment('GoogleBase.insert', 1, sample_rate: 0.1)
```
#### StatsD.gauge
A gauge is a single numerical value value that tells you the state of the system at a point in time. A good example would be the number of messages in a queue.
``` ruby
-StatsD.gauge('GoogleBase.queued', 12, 1.0)
+StatsD.gauge('GoogleBase.queued', 12, sample_rate: 1.0)
```
Normally, you shouldn't update this value too often, and therefore there is no need to sample this kind metric.
@@ -100,7 +100,7 @@ A set keeps track of the number of unique values that have been seen. This is a
``` ruby
# Submit the customer ID to the set. It will only be counted if it hasn't been seen before.
-StatsD.set('GoogleBase.customers', "12345", 1.0)
+StatsD.set('GoogleBase.customers', "12345", sample_rate: 1.0)
```
Because you are counting unique values, the results of using a sampling value less than 1.0 can lead to unexpected, hard to interpret results.
@@ -174,7 +174,6 @@ end
You can instrument class methods, just like instance methods, using the metaprogramming methods. You simply have to configure the instrumentation on the singleton class of the Class you want to instrument.
```ruby
-AWS::S3::Base.singleton_class.extend StatsD::Instrument
AWS::S3::Base.singleton_class.statsd_measure :request, 'S3.request'
```
@@ -189,6 +188,15 @@ passed.
GoogleBase.statsd_count :insert, lamdba{|object, args| object.class.to_s.downcase + "." + args.first.to_s + ".insert" }
```
+### Tags
+
+The Datadog implementation support tags, which you can use to slice and dice metrics in their UI. You can specify a list of tags as an option, either standalone tag (e.g. `"mytag"`), or key value based, separated by a colon: `"env:production"`.
+
+``` ruby
+StatsD.increment('my.counter', tags: ['env:production', 'unicorn'])
+GoogleBase.statsd_count :insert, 'GoogleBase.insert', tags: ['env:production']
+```
+
## Reliance on DNS
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.
View
@@ -31,15 +31,15 @@ def self.generate_metric_name(metric_name, callee, *args)
metric_name.respond_to?(:call) ? metric_name.call(callee, args).gsub('::', '.') : metric_name.gsub('::', '.')
end
- def statsd_measure(method, name, sample_rate = StatsD.default_sample_rate)
+ def statsd_measure(method, name, *metric_options)
add_to_method(method, name, :measure) do |old_method, new_method, metric_name, *args|
define_method(new_method) do |*args, &block|
- StatsD.measure(StatsD::Instrument.generate_metric_name(metric_name, self, *args), nil, sample_rate) { send(old_method, *args, &block) }
+ StatsD.measure(StatsD::Instrument.generate_metric_name(metric_name, self, *args), nil, *metric_options) { send(old_method, *args, &block) }
end
end
end
- def statsd_count_success(method, name, sample_rate = StatsD.default_sample_rate)
+ def statsd_count_success(method, name, *metric_options)
add_to_method(method, name, :count_success) do |old_method, new_method, metric_name|
define_method(new_method) do |*args, &block|
begin
@@ -52,13 +52,13 @@ def statsd_count_success(method, name, sample_rate = StatsD.default_sample_rate)
result
ensure
suffix = truthiness == false ? 'failure' : 'success'
- StatsD.increment("#{StatsD::Instrument.generate_metric_name(metric_name, self, *args)}.#{suffix}", 1, sample_rate)
+ StatsD.increment("#{StatsD::Instrument.generate_metric_name(metric_name, self, *args)}.#{suffix}", 1, *metric_options)
end
end
end
end
- def statsd_count_if(method, name, sample_rate = StatsD.default_sample_rate)
+ def statsd_count_if(method, name, *metric_options)
add_to_method(method, name, :count_if) do |old_method, new_method, metric_name|
define_method(new_method) do |*args, &block|
begin
@@ -70,16 +70,16 @@ def statsd_count_if(method, name, sample_rate = StatsD.default_sample_rate)
truthiness = (yield(result) rescue false) if block_given?
result
ensure
- StatsD.increment(StatsD::Instrument.generate_metric_name(metric_name, self, *args), sample_rate) if truthiness
+ StatsD.increment(StatsD::Instrument.generate_metric_name(metric_name, self, *args), *metric_options) if truthiness
end
end
end
end
- def statsd_count(method, name, sample_rate = StatsD.default_sample_rate)
+ def statsd_count(method, name, *metric_options)
add_to_method(method, name, :count) do |old_method, new_method, metric_name|
define_method(new_method) do |*args, &block|
- StatsD.increment(StatsD::Instrument.generate_metric_name(metric_name, self, *args), 1, sample_rate)
+ StatsD.increment(StatsD::Instrument.generate_metric_name(metric_name, self, *args), 1, *metric_options)
send(old_method, *args, &block)
end
end
@@ -130,39 +130,62 @@ def remove_from_method(method, name, action)
end
# glork:320|ms
- def self.measure(key, milli = nil, sample_rate = default_sample_rate, tags = nil)
+ def self.measure(key, value = nil, *metric_options)
+ if value.is_a?(Hash) && metric_options.empty?
+ metric_options = [value]
+ value = nil
+ end
+
result = nil
- ms = milli || 1000 * Benchmark.realtime do
+ ms = value || 1000 * Benchmark.realtime do
result = yield
end
- collect(key, ms, :ms, sample_rate, tags)
+ collect(:ms, key, ms, hash_argument(metric_options))
result
end
# gorets:1|c
- def self.increment(key, delta = 1, sample_rate = default_sample_rate, tags = nil)
- collect(key, delta, :incr, sample_rate, tags)
+ def self.increment(key, value = 1, *metric_options)
+ if value.is_a?(Hash) && metric_options.empty?
+ metric_options = [value]
+ value = 1
+ end
+
+ collect(:incr, key, value, hash_argument(metric_options))
end
# gaugor:333|g
# guagor:1234|kv|@1339864935 (statsite)
- def self.gauge(key, value, sample_rate_or_epoch = default_sample_rate, tags = nil)
- collect(key, value, :g, sample_rate_or_epoch, tags)
+ def self.gauge(key, value, *metric_options)
+ collect(:g, key, value, hash_argument(metric_options))
end
# histogram:123.45|h
- def self.histogram(key, value, sample_rate_or_epoch = default_sample_rate, tags = nil)
- collect(key, value, :h, sample_rate_or_epoch, tags)
+ def self.histogram(key, value, *metric_options)
+ collect(:h, key, value, hash_argument(metric_options))
end
# uniques:765|s
- def self.set(key, value, sample_rate_or_epoch = default_sample_rate, tags = nil)
- collect(key, value, :s, sample_rate_or_epoch, tags)
+ def self.set(key, value, *metric_options)
+ collect(:s, key, value, hash_argument(metric_options))
end
private
+ def self.hash_argument(args)
+ return {} if args.length == 0
+ return args.first if args.length == 1 && args.first.is_a?(Hash)
+
+ order = [:sample_rate, :tags]
+ hash = {}
+ args.each_with_index do |value, index|
+ hash[order[index]] = value
+ end
+
+ return hash
+ end
+
def self.invalidate_socket
@socket = nil
end
@@ -175,12 +198,13 @@ def self.socket
@socket
end
- def self.collect(k, v, op, sample_rate = default_sample_rate, tags = nil)
+ def self.collect(type, k, v, options = {})
return unless enabled
+ sample_rate = options[:sample_rate] || StatsD.default_sample_rate
return if sample_rate < 1 && rand > sample_rate
- command = generate_packet(k, v, op, sample_rate, tags)
- write_packet(command)
+ packet = generate_packet(type, k, v, sample_rate, options[:tags])
+ write_packet(packet)
end
def self.write_packet(command)
@@ -202,9 +226,9 @@ def self.clean_tags(tags)
end
end
- def self.generate_packet(k, v, op, sample_rate = default_sample_rate, tags = nil)
+ def self.generate_packet(type, k, v, sample_rate = default_sample_rate, tags = nil)
command = "#{self.prefix + '.' if self.prefix}#{k}:#{v}"
- case op
+ case type
when :incr
command << '|c'
when :ms
@@ -51,7 +51,7 @@ class StatsDInstrumentationTest < Test::Unit::TestCase
def test_statsd_count_if
ActiveMerchant::Gateway.statsd_count_if :ssl_post, 'ActiveMerchant.Gateway.if'
- StatsD.expects(:increment).with('ActiveMerchant.Gateway.if', 1).once
+ StatsD.expects(:increment).with('ActiveMerchant.Gateway.if').once
ActiveMerchant::Gateway.new.purchase(true)
ActiveMerchant::Gateway.new.purchase(false)
@@ -63,7 +63,7 @@ def test_statsd_count_if_with_method_receiving_block
result == 'true'
end
- StatsD.expects(:collect).with('ActiveMerchant.Base.post_with_block', 1, :incr, 1.0, nil).once
+ StatsD.expects(:collect).with(:incr, 'ActiveMerchant.Base.post_with_block', 1, {}).once
assert_equal 'true', ActiveMerchant::Base.new.post_with_block { 'true' }
assert_equal 'false', ActiveMerchant::Base.new.post_with_block { 'false' }
@@ -75,7 +75,7 @@ def test_statsd_count_if_with_block
result[:success]
end
- StatsD.expects(:increment).with('ActiveMerchant.Gateway.block', 1).once
+ StatsD.expects(:increment).with('ActiveMerchant.Gateway.block').once
ActiveMerchant::UniqueGateway.new.purchase(true)
ActiveMerchant::UniqueGateway.new.purchase(false)
@@ -99,8 +99,8 @@ def test_statsd_count_success_with_method_receiving_block
result == 'successful'
end
- StatsD.expects(:collect).with('ActiveMerchant.Base.post_with_block.success', 1, :incr, 1.0, nil).once
- StatsD.expects(:collect).with('ActiveMerchant.Base.post_with_block.failure', 1, :incr, 1.0, nil).once
+ StatsD.expects(:collect).with(:incr, 'ActiveMerchant.Base.post_with_block.success', 1, {}).once
+ StatsD.expects(:collect).with(:incr, 'ActiveMerchant.Base.post_with_block.failure', 1, {}).once
assert_equal 'successful', ActiveMerchant::Base.new.post_with_block { 'successful' }
assert_equal 'not so successful', ActiveMerchant::Base.new.post_with_block { 'not so successful' }
@@ -113,10 +113,10 @@ def test_statsd_count_success_with_block
result[:success]
end
- StatsD.expects(:increment).with('ActiveMerchant.Gateway.success', 1, StatsD.default_sample_rate)
+ StatsD.expects(:increment).with('ActiveMerchant.Gateway.success', 1)
ActiveMerchant::UniqueGateway.new.purchase(true)
- StatsD.expects(:increment).with('ActiveMerchant.Gateway.failure', 1, StatsD.default_sample_rate)
+ StatsD.expects(:increment).with('ActiveMerchant.Gateway.failure', 1)
ActiveMerchant::UniqueGateway.new.purchase(false)
ActiveMerchant::UniqueGateway.statsd_remove_count_success :ssl_post, 'ActiveMerchant.Gateway'
@@ -125,7 +125,7 @@ def test_statsd_count_success_with_block
def test_statsd_count
ActiveMerchant::Gateway.statsd_count :ssl_post, 'ActiveMerchant.Gateway.ssl_post'
- StatsD.expects(:increment).with('ActiveMerchant.Gateway.ssl_post', 1, StatsD.default_sample_rate)
+ StatsD.expects(:increment).with('ActiveMerchant.Gateway.ssl_post', 1)
ActiveMerchant::Gateway.new.purchase(true)
ActiveMerchant::Gateway.statsd_remove_count :ssl_post, 'ActiveMerchant.Gateway.ssl_post'
@@ -135,7 +135,7 @@ def test_statsd_count_with_name_as_lambda
metric_namer = lambda { |object, args| object.class.to_s.downcase + ".insert." + args.first.to_s }
ActiveMerchant::Gateway.statsd_count(:ssl_post, metric_namer)
- StatsD.expects(:increment).with('gatewaysubclass.insert.true', 1, StatsD.default_sample_rate)
+ StatsD.expects(:increment).with('gatewaysubclass.insert.true', 1)
GatewaySubClass.new.purchase(true)
ActiveMerchant::Gateway.statsd_remove_count(:ssl_post, metric_namer)
@@ -144,7 +144,7 @@ def test_statsd_count_with_name_as_lambda
def test_statsd_count_with_method_receiving_block
ActiveMerchant::Base.statsd_count :post_with_block, 'ActiveMerchant.Base.post_with_block'
- StatsD.expects(:collect).with('ActiveMerchant.Base.post_with_block', 1, :incr, 1.0, nil)
+ StatsD.expects(:collect).with(:incr, 'ActiveMerchant.Base.post_with_block', 1, {})
assert_equal 'block called', ActiveMerchant::Base.new.post_with_block { 'block called' }
ActiveMerchant::Base.statsd_remove_count :post_with_block, 'ActiveMerchant.Base.post_with_block'
@@ -153,7 +153,7 @@ def test_statsd_count_with_method_receiving_block
def test_statsd_measure_with_nested_modules
ActiveMerchant::UniqueGateway.statsd_measure :ssl_post, 'ActiveMerchant::Gateway.ssl_post'
- StatsD.expects(:measure).with('ActiveMerchant.Gateway.ssl_post', nil, StatsD.default_sample_rate)
+ StatsD.expects(:measure).with('ActiveMerchant.Gateway.ssl_post', nil)
ActiveMerchant::UniqueGateway.new.purchase(true)
ActiveMerchant::UniqueGateway.statsd_remove_measure :ssl_post, 'ActiveMerchant::Gateway.ssl_post'
@@ -171,7 +171,7 @@ def test_statsd_measure
def test_statsd_measure_with_method_receiving_block
ActiveMerchant::Base.statsd_measure :post_with_block, 'ActiveMerchant.Base.post_with_block'
- StatsD.expects(:collect).with('ActiveMerchant.Base.post_with_block', is_a(Float), :ms, 1.0, nil)
+ StatsD.expects(:collect).with(:ms, 'ActiveMerchant.Base.post_with_block', is_a(Float), {})
assert_equal 'block called', ActiveMerchant::Base.new.post_with_block { 'block called' }
ActiveMerchant::Base.statsd_remove_measure :post_with_block, 'ActiveMerchant.Base.post_with_block'
@@ -181,7 +181,17 @@ def test_instrumenting_class_method
ActiveMerchant::Gateway.singleton_class.extend StatsD::Instrument
ActiveMerchant::Gateway.singleton_class.statsd_count :sync, 'ActiveMerchant.Gateway.sync'
- StatsD.expects(:increment).with('ActiveMerchant.Gateway.sync', 1, StatsD.default_sample_rate)
+ StatsD.expects(:increment).with('ActiveMerchant.Gateway.sync', 1)
+ ActiveMerchant::Gateway.sync
+
+ ActiveMerchant::Gateway.singleton_class.statsd_remove_count :sync, 'ActiveMerchant.Gateway.sync'
+ end
+
+ def test_statsd_count_with_tags
+ ActiveMerchant::Gateway.singleton_class.extend StatsD::Instrument
+ ActiveMerchant::Gateway.singleton_class.statsd_count :sync, 'ActiveMerchant.Gateway.sync', :tags => ['t']
+
+ StatsD.expects(:increment).with('ActiveMerchant.Gateway.sync', 1, :tags => ['t'])
ActiveMerchant::Gateway.sync
ActiveMerchant::Gateway.singleton_class.statsd_remove_count :sync, 'ActiveMerchant.Gateway.sync'
View
@@ -16,37 +16,56 @@ def setup
end
def test_statsd_measure_with_explicit_value
- StatsD.expects(:collect).with('values.foobar', 42, :ms, 1.0, nil)
+ StatsD.expects(:collect).with(:ms, 'values.foobar', 42, {})
StatsD.measure('values.foobar', 42)
end
def test_statsd_measure_with_explicit_value_and_sample_rate
- StatsD.expects(:collect).with('values.foobar', 42, :ms, 0.1, nil)
- StatsD.measure('values.foobar', 42, 0.1)
+ StatsD.expects(:collect).with(:ms, 'values.foobar', 42, :sample_rate => 0.1)
+ StatsD.measure('values.foobar', 42, :sample_rate => 0.1)
end
def test_statsd_measure_with_benchmarked_value
Benchmark.stubs(:realtime).returns(1.12)
- StatsD.expects(:collect).with('values.foobar', 1120.0, :ms, 1.0, nil)
- StatsD.measure('values.foobar', nil) do
+ StatsD.expects(:collect).with(:ms, 'values.foobar', 1120.0, {})
+ StatsD.measure('values.foobar') do
#noop
end
end
+
+ def test_statsd_measure_with_benchmarked_value_and_options
+ Benchmark.stubs(:realtime).returns(1.12)
+ StatsD.expects(:collect).with(:ms, 'values.foobar', 1120.0, :sample_rate => 1.0)
+ StatsD.measure('values.foobar', :sample_rate => 1.0) do
+ #noop
+ end
+ end
+
+ def test_statsd_increment_with_hash_argument
+ StatsD.expects(:collect).with(:incr, 'values.foobar', 1, :tags => ['test'])
+ StatsD.increment('values.foobar', :tags => ['test'])
+ end
+
+ def test_statsd_increment_with_multiple_arguments
+ StatsD.expects(:collect).with(:incr, 'values.foobar', 12, :sample_rate => nil, :tags => ['test'])
+ StatsD.increment('values.foobar', 12, nil, ['test'])
+ end
+
def test_statsd_gauge
- StatsD.expects(:collect).with('values.foobar', 12, :g, 1.0, nil)
+ StatsD.expects(:collect).with(:g, 'values.foobar', 12, {})
StatsD.gauge('values.foobar', 12)
end
def test_statsd_set
- StatsD.expects(:collect).with('values.foobar', 12, :s, 1.0, nil)
+ StatsD.expects(:collect).with(:s, 'values.foobar', 12, {})
StatsD.set('values.foobar', 12)
end
def test_statsd_histogram_on_datadog
StatsD.stubs(:implementation).returns(:datadog)
- StatsD.expects(:collect).with('values.hg', 12.33, :h, 0.2, ['tag_123', 'key-name:value123'])
- StatsD.histogram('values.hg', 12.33, 0.2, ['tag_123', 'key-name:value123'])
+ StatsD.expects(:collect).with(:h, 'values.hg', 12.33, :sample_rate => 0.2, :tags => ['tag_123', 'key-name:value123'])
+ StatsD.histogram('values.hg', 12.33, :sample_rate => 0.2, :tags => ['tag_123', 'key-name:value123'])
end
def test_collect_respects_enabled

0 comments on commit ff47432

Please sign in to comment.