Skip to content
Browse files

Merge branch 'jeremy'

* jeremy: (37 commits)
  Remove bogus self-dependency. WTF jeweler?
  Update generated .gemspec too
  Update gemspec info. Use fork URL.
  Socket error handling specs
  Ignore socket errors even if logger is not set.
  Update Bundler install docs to use github: source and limit to ~> 0.4.0
  Bundler installation instructions
  missing spec and doc for guage
  Change rcov to simplecov for compatibility with ruby 1.9
  README cosmetics
  Remove rcov dep
  Travis CI
  Bump to 0.4.0. Shed the .signals prerelease tag.
  Do less work if this stat isn't sampled anyway. h/t github#1
  Bundle up dependencies to run tests
  Revert "Resolve non-IP hosts to an IP immediately rather than incur a DNS lookup for each socket send"
  Fix exception handling specs
  Add an exception handling module so statsd can't disrupt the code it's monitoring.
  Setting host or port to nil sets to defaults
  Allow setting a nil host. Will resolve to localhost ultimately.
  ...

Conflicts:
	lib/statsd.rb
	statsd-ruby.gemspec
  • Loading branch information...
2 parents 70631a7 + ff30ee5 commit 4c323da79109b98a6cd5a2d630a77ac7fdc0ede6 @raggi raggi committed Jul 2, 2012
Showing with 201 additions and 94 deletions.
  1. +1 −1 .gitignore
  2. +5 −0 .travis.yml
  3. +4 −0 Gemfile
  4. +7 −2 README.rdoc
  5. +14 −21 Rakefile
  6. +1 −1 VERSION
  7. +63 −36 lib/statsd.rb
  8. +5 −0 spec/helper.rb
  9. +85 −19 spec/statsd_spec.rb
  10. +16 −14 statsd-ruby.gemspec
View
2 .gitignore
@@ -1,4 +1,4 @@
-# rcov generated
+# simplecov generated
coverage
# rdoc generated
View
5 .travis.yml
@@ -0,0 +1,5 @@
+---
+language: ruby
+rvm:
+- 1.8.7
+- 1.9.3
View
4 Gemfile
@@ -0,0 +1,4 @@
+source 'https://rubygems.org'
+gemspec
+
+gem 'jeweler', '~> 1.8'
View
9 README.rdoc
@@ -1,6 +1,11 @@
-= statsd
+= statsd-ruby {<img src="https://secure.travis-ci.org/jeremy/statsd-ruby.png" />}[http://travis-ci.org/jeremy/statsd-ruby]
-A Ruby statsd client (https://github.com/etsy/statsd)
+A Ruby client for {StatsD}[https://github.com/etsy/statsd]
+
+= Installing
+
+Bundler:
+ gem 'statsd-ruby', '~> 0.4.0', github: 'jeremy/statsd-ruby', require: 'statsd'
= Installing
View
35 Rakefile
@@ -1,20 +1,23 @@
require 'rubygems'
-require 'rake'
+require 'bundler/setup'
+
+task :default => :spec
require 'jeweler'
Jeweler::Tasks.new do |gem|
- # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
- gem.name = "statsd-ruby"
- gem.homepage = "http://github.com/reinh/statsd"
- gem.license = "MIT"
- gem.summary = %Q{A Statsd client in Ruby}
- gem.description = %Q{A Statsd client in Ruby}
- gem.email = "rein@phpfog.com"
- gem.authors = ["Rein Henrichs"]
+ gem.name = "statsd-ruby"
+ gem.license = "MIT"
+ gem.homepage = "http://github.com/jeremy/statsd-ruby"
+ gem.summary = "A Ruby StatsD client"
+ gem.description = "A Ruby StatsD client"
+
+ gem.email = "rein@phpfog.com"
+ gem.authors = ["Rein Henrichs"]
+
gem.add_development_dependency "minitest", ">= 0"
gem.add_development_dependency "yard", "~> 0.6.0"
- gem.add_development_dependency "jeweler", "~> 1.5.2"
- gem.add_development_dependency "rcov", ">= 0"
+ gem.add_development_dependency "jeweler", "~> 1.8"
+ gem.add_development_dependency "simplecov", ">= 0"
end
Jeweler::RubygemsDotOrgTasks.new
@@ -25,15 +28,5 @@ Rake::TestTask.new(:spec) do |spec|
spec.verbose = true
end
-require 'rcov/rcovtask'
-Rcov::RcovTask.new do |spec|
- spec.libs << 'lib' << 'spec'
- spec.pattern = 'spec/**/*_spec.rb'
- spec.verbose = true
- spec.rcov_opts << "--exclude spec,gems"
-end
-
-task :default => :spec
-
require 'yard'
YARD::Rake::YardocTask.new
View
2 VERSION
@@ -1 +1 @@
-0.3.0
+0.4.0
View
99 lib/statsd.rb
@@ -7,75 +7,106 @@
# @example Send some stats
# $statsd.increment 'garets'
# $statsd.timing 'glork', 320
+# $statsd.gauge 'bork', 100
# @example Use {#time} to time the execution of a block
# $statsd.time('account.activate') { @account.activate! }
# @example Create a namespaced statsd client and increment 'account.activate'
# statsd = Statsd.new('localhost').tap{|sd| sd.namespace = 'account'}
# statsd.increment 'activate'
class Statsd
# A namespace to prepend to all statsd calls.
- attr_accessor :namespace
+ attr_reader :namespace
- #characters that will be replaced with _ in stat names
- RESERVED_CHARS_REGEX = /[\:\|\@]/
+ # StatsD host. Defaults to 127.0.0.1.
+ attr_accessor :host
+
+ # StatsD port. Defaults to 8125.
+ attr_accessor :port
class << self
- # Set to any standard logger instance (including stdlib's Logger) to enable
- # stat logging using logger.debug
+ # Set to a standard logger instance to enable debug logging.
attr_accessor :logger
end
# @param [String] host your statsd host
# @param [Integer] port your statsd port
- def initialize(host, port=8125)
- @host, @port = host, port
+ def initialize(host = '127.0.0.1', port = 8125)
+ self.host, self.port = host, port
+ @prefix = nil
+ @socket = UDPSocket.new
+ end
+
+ def namespace=(namespace) #:nodoc:
+ @namespace = namespace
+ @prefix = "#{namespace}."
+ end
+
+ def host=(host) #:nodoc:
+ @host = host || '127.0.0.1'
+ end
+
+ def port=(port) #:nodoc:
+ @port = port || 8125
end
# Sends an increment (count = 1) for the given stat to the statsd server.
#
- # @param stat (see #count)
- # @param sample_rate (see #count)
+ # @param [String] stat stat name
+ # @param [Numeric] sample_rate sample rate, 1 for always
# @see #count
- def increment(stat, sample_rate=1); count stat, 1, sample_rate end
+ def increment(stat, sample_rate=1)
+ count stat, 1, sample_rate
+ end
# Sends a decrement (count = -1) for the given stat to the statsd server.
#
- # @param stat (see #count)
- # @param sample_rate (see #count)
+ # @param [String] stat stat name
+ # @param [Numeric] sample_rate sample rate, 1 for always
# @see #count
- def decrement(stat, sample_rate=1); count stat, -1, sample_rate end
+ def decrement(stat, sample_rate=1)
+ count stat, -1, sample_rate
+ end
# Sends an arbitrary count for the given stat to the statsd server.
#
# @param [String] stat stat name
# @param [Integer] count count
- # @param [Integer] sample_rate sample rate, 1 for always
- def count(stat, count, sample_rate=1); send stat, count, 'c', sample_rate end
+ # @param [Numeric] sample_rate sample rate, 1 for always
+ def count(stat, count, sample_rate=1)
+ send_stats stat, count, :c, sample_rate
+ end
# Sends an arbitary gauge value for the given stat to the statsd server.
#
+ # This is useful for recording things like available disk space,
+ # memory usage, and the like, which have different semantics than
+ # counters.
+ #
# @param [String] stat stat name.
# @param [Numeric] gauge value.
+ # @param [Numeric] sample_rate sample rate, 1 for always
# @example Report the current user count:
# $statsd.gauge('user.count', User.count)
- def gauge(stat, value)
- send stat, value, 'g'
+ def gauge(stat, value, sample_rate=1)
+ send_stats stat, value, :g, sample_rate
end
# Sends a timing (in ms) for the given stat to the statsd server. The
# sample_rate determines what percentage of the time this report is sent. The
# statsd server then uses the sample_rate to correctly track the average
# timing for the stat.
#
- # @param stat stat name
+ # @param [String] stat stat name
# @param [Integer] ms timing in milliseconds
- # @param [Integer] sample_rate sample rate, 1 for always
- def timing(stat, ms, sample_rate=1); send stat, ms, 'ms', sample_rate end
+ # @param [Numeric] sample_rate sample rate, 1 for always
+ def timing(stat, ms, sample_rate=1)
+ send_stats stat, ms, :ms, sample_rate
+ end
# Reports execution time of the provided block using {#timing}.
#
- # @param stat (see #timing)
- # @param sample_rate (see #timing)
+ # @param [String] stat stat name
+ # @param [Numeric] sample_rate sample rate, 1 for always
# @yield The operation to be timed
# @see #timing
# @example Report the time (in ms) taken to activate an account
@@ -89,24 +120,20 @@ def time(stat, sample_rate=1)
private
- def sampled(sample_rate)
- yield unless sample_rate < 1 and rand > sample_rate
- end
-
- def send(stat, delta, type, sample_rate=1)
- sampled(sample_rate) do
- prefix = "#{@namespace}." unless @namespace.nil?
- stat = stat.to_s.gsub('::', '.').gsub(RESERVED_CHARS_REGEX, '_')
- send_to_socket("#{prefix}#{stat}:#{delta}|#{type}#{'|@' << sample_rate.to_s if sample_rate < 1}")
+ def send_stats(stat, delta, type, sample_rate=1)
+ if sample_rate == 1 or rand < sample_rate
+ # Replace Ruby module scoping with '.' and reserved chars (: | @) with underscores.
+ stat = stat.to_s.gsub('::', '.').tr(':|@', '_')
+ rate = "|@#{sample_rate}" unless sample_rate == 1
+ send_to_socket "#{@prefix}#{stat}:#{delta}|#{type}#{rate}"
end
end
def send_to_socket(message)
- self.class.logger.debug {"Statsd: #{message}"} if self.class.logger
- socket.send(message, 0, @host, @port)
+ self.class.logger.debug { "Statsd: #{message}" } if self.class.logger
+ @socket.send(message, 0, @host, @port)
rescue => boom
- self.class.logger.error {"Statsd: #{boom.class} #{boom}"} if self.class.logger
+ self.class.logger.error { "Statsd: #{boom.class} #{boom}" } if self.class.logger
+ nil
end
-
- def socket; @socket ||= UDPSocket.new end
end
View
5 spec/helper.rb
@@ -1,8 +1,13 @@
require 'rubygems'
+require 'bundler/setup'
require 'minitest/autorun'
$LOAD_PATH.unshift(File.dirname(__FILE__))
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
+
+require 'simplecov'
+SimpleCov.start
+
require 'statsd'
require 'logger'
View
104 spec/statsd_spec.rb
@@ -1,13 +1,14 @@
require 'helper'
describe Statsd do
+ class Statsd
+ # we need to stub this
+ attr_accessor :socket
+ end
+
before do
@statsd = Statsd.new('localhost', 1234)
- class << @statsd
- public :sampled # we need to test this
- attr_reader :host, :port # we also need to test this
- def socket; @socket ||= FakeUDPSocket.new end
- end
+ @statsd.socket = FakeUDPSocket.new
end
after { @statsd.socket.clear }
@@ -18,8 +19,34 @@ def socket; @socket ||= FakeUDPSocket.new end
@statsd.port.must_equal 1234
end
- it "should default the port to 8125" do
- Statsd.new('localhost').instance_variable_get('@port').must_equal 8125
+ it "should default the host to 127.0.0.1 and port to 8125" do
+ statsd = Statsd.new
+ statsd.host.must_equal '127.0.0.1'
+ statsd.port.must_equal 8125
+ end
+ end
+
+ describe "#host and #port" do
+ it "should set host and port" do
+ @statsd.host = '1.2.3.4'
+ @statsd.port = 5678
+ @statsd.host.must_equal '1.2.3.4'
+ @statsd.port.must_equal 5678
+ end
+
+ it "should not resolve hostnames to IPs" do
+ @statsd.host = 'localhost'
+ @statsd.host.must_equal 'localhost'
+ end
+
+ it "should set nil host to default" do
+ @statsd.host = nil
+ @statsd.host.must_equal '127.0.0.1'
+ end
+
+ it "should set nil port to default" do
+ @statsd.port = nil
+ @statsd.port.must_equal 8125
end
end
@@ -53,6 +80,23 @@ def socket; @socket ||= FakeUDPSocket.new end
end
end
+ describe "#gauge" do
+ it "should send a message with a 'g' type, per the nearbuy fork" do
+ @statsd.gauge('begrutten-suffusion', 536)
+ @statsd.socket.recv.must_equal ['begrutten-suffusion:536|g']
+ @statsd.gauge('begrutten-suffusion', -107.3)
+ @statsd.socket.recv.must_equal ['begrutten-suffusion:-107.3|g']
+ end
+
+ describe "with a sample rate" do
+ before { class << @statsd; def rand; 0; end; end } # ensure delivery
+ it "should format the message according to the statsd spec" do
+ @statsd.gauge('begrutten-suffusion', 536, 0.1)
+ @statsd.socket.recv.must_equal ['begrutten-suffusion:536|g|@0.1']
+ end
+ end
+ end
+
describe "#timing" do
it "should format the message according to the statsd spec" do
@statsd.timing('foobar', 500)
@@ -91,29 +135,33 @@ def socket; @socket ||= FakeUDPSocket.new end
describe "#sampled" do
describe "when the sample rate is 1" do
- it "should yield" do
- @statsd.sampled(1) { :yielded }.must_equal :yielded
+ before { class << @statsd; def rand; raise end; end }
+ it "should send" do
+ @statsd.timing('foobar', 500, 1)
+ @statsd.socket.recv.must_equal ['foobar:500|ms']
end
end
describe "when the sample rate is greater than a random value [0,1]" do
before { class << @statsd; def rand; 0; end; end } # ensure delivery
- it "should yield" do
- @statsd.sampled(0.5) { :yielded }.must_equal :yielded
+ it "should send" do
+ @statsd.timing('foobar', 500, 0.5)
+ @statsd.socket.recv.must_equal ['foobar:500|ms|@0.5']
end
end
describe "when the sample rate is less than a random value [0,1]" do
before { class << @statsd; def rand; 1; end; end } # ensure no delivery
- it "should not yield" do
- @statsd.sampled(0.5) { :yielded }.must_equal nil
+ it "should not send" do
+ @statsd.timing('foobar', 500, 0.5).must_equal nil
end
end
describe "when the sample rate is equal to a random value [0,1]" do
- before { class << @statsd; def rand; 0.5; end; end } # ensure delivery
- it "should yield" do
- @statsd.sampled(0.5) { :yielded }.must_equal :yielded
+ before { class << @statsd; def rand; 0; end; end } # ensure delivery
+ it "should send" do
+ @statsd.timing('foobar', 500, 0.5)
+ @statsd.socket.recv.must_equal ['foobar:500|ms|@0.5']
end
end
end
@@ -135,6 +183,11 @@ def socket; @socket ||= FakeUDPSocket.new end
@statsd.timing('foobar', 500)
@statsd.socket.recv.must_equal ['service.foobar:500|ms']
end
+
+ it "should add namespace to gauge" do
+ @statsd.gauge('foobar', 500)
+ @statsd.socket.recv.must_equal ['service.foobar:500|g']
+ end
end
describe "with logging" do
@@ -156,11 +209,9 @@ def socket; @socket ||= FakeUDPSocket.new end
@log.string.must_be_empty
end
-
end
describe "stat names" do
-
it "should accept anything as stat" do
@statsd.increment(Object, 1)
end
@@ -176,9 +227,24 @@ class Statsd::SomeClass; end
@statsd.increment('ray@hostname.blah|blah.blah:blah', 1)
@statsd.socket.recv.must_equal ['ray_hostname.blah_blah.blah_blah:1|c']
end
-
end
+ describe "handling socket errors" do
+ before do
+ require 'stringio'
+ Statsd.logger = Logger.new(@log = StringIO.new)
+ @statsd.socket.instance_eval { def send(*) raise SocketError end }
+ end
+
+ it "should ignore socket errors" do
+ @statsd.increment('foobar').must_equal nil
+ end
+
+ it "should log socket errors" do
+ @statsd.increment('foobar')
+ @log.string.must_match 'Statsd: SocketError'
+ end
+ end
end
describe Statsd do
View
30 statsd-ruby.gemspec
@@ -4,20 +4,22 @@
# -*- encoding: utf-8 -*-
Gem::Specification.new do |s|
- s.name = %q{statsd-ruby}
- s.version = "0.3.0"
+ s.name = "statsd-ruby"
+ s.version = "0.4.0"
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["Rein Henrichs"]
- s.date = %q{2011-06-24}
- s.description = %q{A Statsd client in Ruby}
- s.email = %q{rein@phpfog.com}
+ s.date = "2012-05-30"
+ s.description = "A Ruby StatsD client"
+ s.email = "rein@phpfog.com"
s.extra_rdoc_files = [
"LICENSE.txt",
"README.rdoc"
]
s.files = [
".document",
+ ".travis.yml",
+ "Gemfile",
"LICENSE.txt",
"README.rdoc",
"Rakefile",
@@ -27,31 +29,31 @@ Gem::Specification.new do |s|
"spec/statsd_spec.rb",
"statsd-ruby.gemspec"
]
- s.homepage = %q{http://github.com/reinh/statsd}
+ s.homepage = "http://github.com/jeremy/statsd-ruby"
s.licenses = ["MIT"]
s.require_paths = ["lib"]
- s.rubygems_version = %q{1.3.9.1}
- s.summary = %q{A Statsd client in Ruby}
+ s.rubygems_version = "1.8.23"
+ s.summary = "A Ruby StatsD client"
if s.respond_to? :specification_version then
s.specification_version = 3
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
s.add_development_dependency(%q<minitest>, [">= 0"])
s.add_development_dependency(%q<yard>, ["~> 0.6.0"])
- s.add_development_dependency(%q<jeweler>, ["~> 1.5.2"])
- s.add_development_dependency(%q<rcov>, [">= 0"])
+ s.add_development_dependency(%q<jeweler>, ["~> 1.8"])
+ s.add_development_dependency(%q<simplecov>, [">= 0"])
else
+ s.add_dependency(%q<jeweler>, ["~> 1.8"])
s.add_dependency(%q<minitest>, [">= 0"])
s.add_dependency(%q<yard>, ["~> 0.6.0"])
- s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
- s.add_dependency(%q<rcov>, [">= 0"])
+ s.add_dependency(%q<simplecov>, [">= 0"])
end
else
+ s.add_dependency(%q<jeweler>, ["~> 1.8"])
s.add_dependency(%q<minitest>, [">= 0"])
s.add_dependency(%q<yard>, ["~> 0.6.0"])
- s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
- s.add_dependency(%q<rcov>, [">= 0"])
+ s.add_dependency(%q<simplecov>, [">= 0"])
end
end

0 comments on commit 4c323da

Please sign in to comment.
Something went wrong with that request. Please try again.