class Statsd

Statsd: A Statsd client (github.com/etsy/statsd)

@example Set up a global Statsd client for a server on localhost:8125

$statsd = Statsd.new 'localhost', 8125

@example Set up a global Statsd client for a server on IPv6 port 8125

$statsd = Statsd.new '::1', 8125

@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'

Statsd instances are thread safe for general usage, by utilizing the thread safe nature of UDP sends. The attributes are stateful, and are not mutexed, it is expected that users will not change these at runtime in threaded environments. If users require such use cases, it is recommend that users either mutex around their Statsd object, or create separate objects for each namespace / host+port combination.

Attributes

logger[RW]

Set to a standard logger instance to enable debug logging.

batch_size[RW]

The default batch size for new batches (default: 10)

delimiter[R]
The replacement of

on ruby module names when transformed to statsd metric names

host[R]

StatsD host. Defaults to 127.0.0.1.

namespace[R]

A namespace to prepend to all statsd calls.

port[R]

StatsD port. Defaults to 8125.

postfix[R]

a postfix to append to all metrics

prefix[R]

StatsD namespace prefix, generated from namespace

Public Class Methods

new(host = '127.0.0.1', port = 8125, protocol = :udp) click to toggle source

@param [String] host your statsd host @param [Integer] port your statsd port @param [Symbol] :tcp for TCP, :udp or any other value for UDP

# File lib/statsd.rb, line 264
def initialize(host = '127.0.0.1', port = 8125, protocol = :udp)
  @host = host || '127.0.0.1'
  @port = port || 8125
  self.delimiter = "."
  @prefix = nil
  @batch_size = 10
  @postfix = nil
  @socket = nil
  @protocol = protocol || :udp
  @s_mu = Mutex.new
  connect
end

Public Instance Methods

batch(&block) click to toggle source

Creates and yields a Batch that can be used to batch instrument reports into larger packets. Batches are sent either when the packet is “full” (defined by batch_size), or when the block completes, whichever is the sooner.

@yield [Batch] a statsd subclass that collects and batches instruments @example Batch two instument operations:

$statsd.batch do |batch|
  batch.increment 'sys.requests'
  batch.gauge('user.count', User.count)
end
# File lib/statsd.rb, line 403
def batch(&block)
  Batch.new(self).easy(&block)
end
connect() click to toggle source

Reconnects the socket, useful if the address of the statsd has changed. This method is not thread safe from a perspective of stat submission. It is safe from resource leaks. Users do not normally need to call this, but calling it may be appropriate when reconfiguring a process (e.g. from HUP).

# File lib/statsd.rb, line 411
def connect
  @s_mu.synchronize do
    begin
      @socket.close if @socket
    rescue
      # Errors are ignored on reconnects.
    end

    case @protocol
    when :tcp
      @socket = TCPSocket.new @host, @port
    else
      @socket = UDPSocket.new Addrinfo.ip(@host).afamily
      @socket.connect host, port
    end
  end
end
count(stat, count, opts={}) click to toggle source

Sends an arbitrary count for the given stat to the statsd server.

@param [String] stat stat name @param [Integer] count count

# File lib/statsd.rb, line 334
def count(stat, count, opts={})
  send_stats stat, count, :c, opts
end
decrement(stat, opts={}) click to toggle source

Sends a decrement (count = -1) for the given stat to the statsd server.

@param [String] stat stat name @see count

# File lib/statsd.rb, line 326
def decrement(stat, opts={})
  count stat, -1, opts
end
delimiter=(delimiter) click to toggle source

@attribute [w] stat_delimiter

Allows for custom delimiter replacement for :: when Ruby modules are transformed to statsd metric name
# File lib/statsd.rb, line 310
def delimiter=(delimiter)
  @delimiter = delimiter || "."
end
gauge(stat, value, opts={}) click to toggle source

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] value gauge value. @example Report the current user count:

$statsd.gauge('user.count', User.count)
# File lib/statsd.rb, line 348
def gauge(stat, value, opts={})
  send_stats stat, value, :g, opts
end
host=(host) click to toggle source

@attribute [w] host

Writes are not thread safe.
Users should call hup after making changes.
# File lib/statsd.rb, line 297
def host=(host)
  @host = host || '127.0.0.1'
end
increment(stat, opts={}) click to toggle source

Sends an increment (count = 1) for the given stat to the statsd server.

@param [String] stat stat name @see count

# File lib/statsd.rb, line 318
def increment(stat, opts={})
  count stat, 1, opts
end
namespace=(namespace) click to toggle source

@attribute [w] namespace

Writes are not thread safe.
# File lib/statsd.rb, line 279
def namespace=(namespace)
  @namespace = namespace
  @prefix = "#{namespace}."
end
port=(port) click to toggle source

@attribute [w] port

Writes are not thread safe.
Users should call hup after making changes.
# File lib/statsd.rb, line 304
def port=(port)
  @port = port || 8125
end
postfix=(pf) click to toggle source

@attribute [w] postfix

A value to be appended to the stat name after a '.'. If the value is
blank then the postfix will be reset to nil (rather than to '.').
# File lib/statsd.rb, line 287
def postfix=(pf)
  case pf
  when nil, false, '' then @postfix = nil
  else @postfix = ".#{pf}"
  end
end
set(stat, value, opts={}) click to toggle source

Sends an arbitary set value for the given stat to the statsd server.

This is for recording counts of unique events, which are useful to see on graphs to correlate to other values. For example, a deployment might get recorded as a set, and be drawn as annotations on a CPU history graph.

@param [String] stat stat name. @param [Numeric] value event value. @example Report a deployment happening:

$statsd.set('deployment', DEPLOYMENT_EVENT_CODE)
# File lib/statsd.rb, line 363
def set(stat, value, opts={})
  send_stats stat, value, :s, opts
end
time(stat, opts={}) { || ... } click to toggle source

Reports execution time of the provided block using {#timing}.

@param [String] stat stat name @yield The operation to be timed @see timing @example Report the time (in ms) taken to activate an account

$statsd.time('account.activate') { @account.activate! }
# File lib/statsd.rb, line 385
def time(stat, opts={})
  start = Time.now
  result = yield
ensure
  timing(stat, ((Time.now - start) * 1000).round, opts)
  result
end
timing(stat, ms, opts={}) click to toggle source

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 [String] stat stat name @param [Integer] ms timing in milliseconds

# File lib/statsd.rb, line 374
def timing(stat, ms, opts={})
  send_stats stat, ms, :ms, opts
end

Protected Instance Methods

send_to_socket(message) click to toggle source
# File lib/statsd.rb, line 431
def send_to_socket(message)
  self.class.logger.debug { "Statsd: #{message}" } if self.class.logger

  retries = 0
  n = 0
  while true
    # send(2) is atomic, however, in stream cases (TCP) the socket is left
    # in an inconsistent state if a partial message is written. If that case
    # occurs, the socket is closed down and we retry on a new socket.
    n = socket.write(message)

    if n == message.length
      break
    end

    connect
    retries += 1
    raise "statsd: Failed to send after #{retries} attempts" if retries >= 5
  end
  n
rescue => boom
  self.class.logger.error { "Statsd: #{boom.class} #{boom}" } if self.class.logger
  nil
end

Private Instance Methods

send_stats(stat, delta, type, opts = {}) click to toggle source
# File lib/statsd.rb, line 458
def send_stats(stat, delta, type, opts = {})
  sample_rate = opts[: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('::', delimiter).tr(':|@', '_')
    if opts[:tags]
      tags = opts[:tags].map { |k,v| "#{k}=#{v}"}.join(',')
      formatted_tags = "##{tags}"
    end
    rate = "|@#{sample_rate}" unless sample_rate == 1
    send_to_socket "#{prefix}#{stat}#{postfix}#{formatted_tags}:#{delta}|#{type}#{rate}"
  end
end
socket() click to toggle source
# File lib/statsd.rb, line 472
def socket
  # Subtle: If the socket is half-way through initialization in connect, it
  # cannot be used yet.
  @s_mu.synchronize { @socket } || raise(ThreadError, "socket missing")
end