[
  {
    "path": ".document",
    "content": "lib/**/*.rb\nbin/*\n- \nfeatures/**/*.feature\nLICENSE.txt\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: Ruby\n\non: [push, pull_request]\n\njobs:\n  build:\n    strategy:\n      matrix:\n        os:\n          - ubuntu\n          - macos\n        ruby:\n          - 2.4\n          - 2.5\n          - 2.6\n          - 2.7\n          - '3.0'\n          - 3.1\n          - 3.2\n          # TODO: jruby, rbx\n\n    runs-on: ${{ matrix.os }}-latest\n\n    steps:\n    - uses: actions/checkout@v3\n\n    - name: Set up Ruby\n      uses: ruby/setup-ruby@v1\n      with:\n        ruby-version: ${{ matrix.ruby }}\n        bundler: 2.3.26\n\n    - name: Build and test with Rake\n      run: |\n        bundle install\n        bundle exec rake\n"
  },
  {
    "path": ".gitignore",
    "content": "# simplecov generated\ncoverage\n\n# rdoc generated\nrdoc\n\n# yard generated\ndoc\n.yardoc\n\n# bundler\n.bundle\nGemfile.lock\n\n# jeweler generated\npkg\n\n# Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore: \n#\n# * Create a file at ~/.gitignore\n# * Include files you want ignored\n# * Run: git config --global core.excludesfile ~/.gitignore\n#\n# After doing this, these files will be ignored in all your git projects,\n# saving you from having to 'pollute' every project you touch with them\n#\n# 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)\n#\n# For MacOS:\n#\n#.DS_Store\n#\n# For TextMate\n#*.tmproj\n#tmtags\n#\n# For emacs:\n#*~\n#\\#*\n#.\\#*\n#\n# For vim:\n#*.swp\n"
  },
  {
    "path": "Gemfile",
    "content": "source 'https://rubygems.org'\ngemspec\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "Copyright (c) 2011, 2012, 2013 Rein Henrichs\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.rdoc",
    "content": "= statsd-ruby CI: {<img src=\"https://github.com/reinh/statsd/workflows/Ruby/badge.svg\" />}[https://github.com/reinh/statsd/actions?query=workflow%3ARuby]\n\nA Ruby client for {StatsD}[https://github.com/etsy/statsd]\n\n= Installing\n\nBundler:\n  gem \"statsd-ruby\"\n\n= Basic Usage\n\n  # Set up a global Statsd client for a server on localhost:9125\n  $statsd = Statsd.new 'localhost', 9125\n\n  # Set up a global Statsd client for a server on IPv6 port 9125\n  $statsd = Statsd.new '::1', 9125\n\n  # Send some stats\n  $statsd.increment 'garets'\n  $statsd.timing 'glork', 320\n  $statsd.gauge 'bork', 100\n\n  # Use {#time} to time the execution of a block\n  $statsd.time('account.activate') { @account.activate! }\n\n  # Create a namespaced statsd client and increment 'account.activate'\n  statsd = Statsd.new('localhost').tap{|sd| sd.namespace = 'account'}\n  statsd.increment 'activate'\n\n= Testing\n\nRun the specs with <tt>rake spec</tt>\n\n= Performance\n\n* 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).\n\n= Extensions / Libraries / Extra Docs\n\n* See the wiki[https://github.com/reinh/statsd/wiki]\n\n== Contributing to statsd\n\n* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet\n* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it\n* Fork the project\n* Start a feature/bugfix branch\n* Commit and push until you are happy with your contribution\n* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.\n* 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.\n\n== Contributors\n\n* Rein Henrichs\n* Alex Williams\n* Andrew Meyer\n* Chris Gaffney\n* Cody Cutrer\n* Corey Donohoe\n* Dotan Nahum\n* Erez Rabih\n* Eric Chapweske\n* Gabriel Burt\n* Hannes Georg\n* James Tucker\n* Jeremy Kemper\n* John Nunemaker\n* Lann Martin\n* Mahesh Murthy\n* Manu J\n* Matt Sanford\n* Nate Bird\n* Noah Lorang\n* Oscar Del Ben\n* Peter Mounce\n* Ray Krueger\n* Reed Lipman\n* rick\n* Ryan Tomayko\n* Schuyler Erle\n* Thomas Whaples\n* Trae Robrock\n\n== Copyright\n\nCopyright (c) 2011, 2012, 2013 Rein Henrichs. See LICENSE.txt for further details.\n"
  },
  {
    "path": "Rakefile",
    "content": "require 'bundler/setup'\nrequire 'bundler/gem_tasks'\n\ntask :default => :spec\n\nrequire 'rake/testtask'\nRake::TestTask.new(:spec) do |spec|\n  spec.libs << 'lib' << 'spec'\n  spec.pattern = 'spec/**/*_spec.rb'\n  spec.verbose = true\n  spec.warning = true\nend\n\nrequire 'yard'\nYARD::Rake::YardocTask.new\n"
  },
  {
    "path": "lib/statsd/monotonic_time.rb",
    "content": "class Statsd\n  # = MonotonicTime: a helper for getting monotonic time\n  #\n  # @example\n  #   MonotonicTime.time_in_ms #=> 287138801.144576\n\n  # MonotonicTime guarantees that the time is strictly linearly\n  # increasing (unlike realtime).\n  # @see http://pubs.opengroup.org/onlinepubs/9699919799/functions/clock_getres.html\n  module MonotonicTime\n    class << self\n      # @return [Integer] current monotonic time in milliseconds\n      def time_in_ms\n        time_in_nanoseconds / (10.0 ** 6)\n      end\n\n      private\n\n      if defined?(Process::CLOCK_MONOTONIC)\n        def time_in_nanoseconds\n          Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond)\n        end\n      elsif RUBY_ENGINE == 'jruby'\n        def time_in_nanoseconds\n          java.lang.System.nanoTime\n        end\n      else\n        def time_in_nanoseconds\n          t = Time.now\n          t.to_i * (10 ** 9) + t.nsec\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/statsd-ruby.rb",
    "content": "require 'statsd'\n"
  },
  {
    "path": "lib/statsd.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'socket'\nrequire 'forwardable'\nrequire 'json'\n\nrequire 'statsd/monotonic_time'\n\n# = Statsd: A Statsd client (https://github.com/etsy/statsd)\n#\n# @example Set up a global Statsd client for a server on localhost:8125\n#   $statsd = Statsd.new 'localhost', 8125\n# @example Set up a global Statsd client for a server on IPv6 port 8125\n#   $statsd = Statsd.new '::1', 8125\n# @example Send some stats\n#   $statsd.increment 'garets'\n#   $statsd.timing 'glork', 320\n#   $statsd.gauge 'bork', 100\n# @example Use {#time} to time the execution of a block\n#   $statsd.time('account.activate') { @account.activate! }\n# @example Create a namespaced statsd client and increment 'account.activate'\n#   statsd = Statsd.new('localhost').tap{|sd| sd.namespace = 'account'}\n#   statsd.increment 'activate'\n#\n# Statsd instances are thread safe for general usage, by utilizing the thread\n# safe nature of UDP sends. The attributes are stateful, and are not\n# mutexed, it is expected that users will not change these at runtime in\n# threaded environments. If users require such use cases, it is recommend that\n# users either mutex around their Statsd object, or create separate objects for\n# each namespace / host+port combination.\nclass Statsd\n\n  # = Batch: A batching statsd proxy\n  #\n  # @example Batch a set of instruments using Batch and manual flush:\n  #   $statsd = Statsd.new 'localhost', 8125\n  #   batch = Statsd::Batch.new($statsd)\n  #   batch.increment 'garets'\n  #   batch.timing 'glork', 320\n  #   batch.gauge 'bork', 100\n  #   batch.flush\n  #\n  # Batch is a subclass of Statsd, but with a constructor that proxies to a\n  # normal Statsd instance. It has it's own batch_size and namespace parameters\n  # (that inherit defaults from the supplied Statsd instance). It is recommended\n  # that some care is taken if setting very large batch sizes. If the batch size\n  # exceeds the allowed packet size for UDP on your network, communication\n  # troubles may occur and data will be lost.\n  class Batch < Statsd\n\n    extend Forwardable\n    def_delegators :@statsd,\n      :namespace, :namespace=,\n      :host, :host=,\n      :port, :port=,\n      :prefix,\n      :postfix,\n      :delimiter, :delimiter=\n\n    attr_accessor :batch_size, :batch_byte_size, :flush_interval\n\n    # @param [Statsd] statsd requires a configured Statsd instance\n    def initialize(statsd)\n      @statsd = statsd\n      @batch_size = statsd.batch_size\n      @batch_byte_size = statsd.batch_byte_size\n      @flush_interval = statsd.flush_interval\n      @backlog = []\n      @backlog_bytesize = 0\n      @last_flush = Time.now\n    end\n\n    # @yield [Batch] yields itself\n    #\n    # A convenience method to ensure that data is not lost in the event of an\n    # exception being thrown. Batches will be transmitted on the parent socket\n    # as soon as the batch is full, and when the block finishes.\n    def easy\n      yield self\n    ensure\n      flush\n    end\n\n    def flush\n      unless @backlog.empty?\n        @statsd.send_to_socket @backlog.join(\"\\n\")\n        @backlog.clear\n        @backlog_bytesize = 0\n        @last_flush = Time.now\n      end\n    end\n\n    protected\n\n    def send_to_socket(message)\n      # this message wouldn't fit; flush the queue. note that we don't have\n      # to do this for message based flushing, because we're incrementing by\n      # one, so the post-queue check will always catch it\n      if (@batch_byte_size && @backlog_bytesize + message.bytesize + 1 > @batch_byte_size) ||\n          (@flush_interval && last_flush_seconds_ago >= @flush_interval)\n        flush\n      end\n      @backlog << message\n      @backlog_bytesize += message.bytesize\n      # skip the interleaved newline for the first item\n      @backlog_bytesize += 1 if @backlog.length != 1\n      # if we're precisely full now, flush\n      if (@batch_size && @backlog.size == @batch_size) ||\n          (@batch_byte_size && @backlog_bytesize == @batch_byte_size)\n        flush\n      end\n    end\n\n    def last_flush_seconds_ago\n      Time.now - @last_flush\n    end\n\n  end\n\n  class Admin\n    # StatsD host. Defaults to 127.0.0.1.\n    attr_reader :host\n\n    # StatsD admin port. Defaults to 8126.\n    attr_reader :port\n\n    class << self\n      # Set to a standard logger instance to enable debug logging.\n      attr_accessor :logger\n    end\n\n    # @attribute [w] host.\n    #   Users should call connect after changing this.\n    def host=(host)\n      @host = host || '127.0.0.1'\n    end\n\n    # @attribute [w] port.\n    #   Users should call connect after changing this.\n    def port=(port)\n      @port = port || 8126\n    end\n\n    # @param [String] host your statsd host\n    # @param [Integer] port your statsd port\n    def initialize(host = '127.0.0.1', port = 8126)\n      @host = host || '127.0.0.1'\n      @port = port || 8126\n      # protects @socket transactions\n      @socket = nil\n      @s_mu = Mutex.new\n      connect\n    end\n\n    # Reads all gauges from StatsD.\n    def gauges\n      read_metric :gauges\n    end\n\n    # Reads all timers from StatsD.\n    def timers\n      read_metric :timers\n    end\n\n    # Reads all counters from StatsD.\n    def counters\n      read_metric :counters\n    end\n\n    # @param[String] item\n    #   Deletes one or more gauges. Wildcards are allowed.\n    def delgauges item\n      delete_metric :gauges, item\n    end\n\n    # @param[String] item\n    #   Deletes one or more timers. Wildcards are allowed.\n    def deltimers item\n      delete_metric :timers, item\n    end\n\n    # @param[String] item\n    #   Deletes one or more counters. Wildcards are allowed.\n    def delcounters item\n      delete_metric :counters, item\n    end\n\n    def stats\n      result = @s_mu.synchronize do\n        # the format of \"stats\" isn't JSON, who knows why\n        send_to_socket \"stats\"\n        read_from_socket\n      end\n      items = {}\n      result.split(\"\\n\").each do |line|\n        key, val = line.chomp.split(\": \")\n        items[key] = val.to_i\n      end\n      items\n    end\n\n    # Reconnects the socket, for when the statsd address may have changed. Users\n    # do not normally need to call this, but calling it may be appropriate when\n    # reconfiguring a process (e.g. from HUP)\n    def connect\n      @s_mu.synchronize do\n        begin\n          @socket.flush rescue nil\n          @socket.close if @socket\n        rescue\n          # Ignore socket errors on close.\n        end\n        @socket = TCPSocket.new(host, port)\n      end\n    end\n\n    private\n\n    def read_metric name\n      result = @s_mu.synchronize do\n        send_to_socket name\n        read_from_socket\n      end\n      # for some reason, the reply looks like JSON, but isn't, quite\n      JSON.parse result.gsub(\"'\", \"\\\"\")\n    end\n\n    def delete_metric name, item\n      result = @s_mu.synchronize do\n        send_to_socket \"del#{name} #{item}\"\n        read_from_socket\n      end\n      deleted = []\n      result.split(\"\\n\").each do |line|\n        deleted << line.chomp.split(\": \")[-1]\n      end\n      deleted\n    end\n\n    def send_to_socket(message)\n      self.class.logger.debug { \"Statsd: #{message}\" } if self.class.logger\n      @socket.write(message.to_s + \"\\n\")\n    rescue => boom\n      self.class.logger.error { \"Statsd: #{boom.class} #{boom}\" } if self.class.logger\n      nil\n    end\n\n\n    def read_from_socket\n      buffer = \"\"\n      loop do\n        line = @socket.readline\n        break if line == \"END\\n\"\n        buffer += line\n      end\n      @socket.readline # clear the closing newline out of the socket\n      buffer\n    end\n  end\n\n  # A namespace to prepend to all statsd calls.\n  attr_reader :namespace\n\n  # StatsD host. Defaults to 127.0.0.1.\n  attr_reader :host\n\n  # StatsD port. Defaults to 8125.\n  attr_reader :port\n\n  # StatsD namespace prefix, generated from #namespace\n  attr_reader :prefix\n\n  # The default batch size for new batches. Set to nil to use batch_byte_size (default: 10)\n  attr_accessor :batch_size\n\n  # The default batch size, in bytes, for new batches (default: default nil; use batch_size)\n  attr_accessor :batch_byte_size\n\n  # The flush interval, in seconds, for new batches (default: nil)\n  attr_accessor :flush_interval\n\n  # a postfix to append to all metrics\n  attr_reader :postfix\n\n  # The replacement of :: on ruby module names when transformed to statsd metric names\n  attr_reader :delimiter\n\n  class << self\n    # Set to a standard logger instance to enable debug logging.\n    attr_accessor :logger\n  end\n\n  # @param [String] host your statsd host\n  # @param [Integer] port your statsd port\n  # @param [Symbol] protocol :tcp for TCP, :udp or any other value for UDP\n  def initialize(host = '127.0.0.1', port = 8125, protocol = :udp)\n    @host = host || '127.0.0.1'\n    @port = port || 8125\n    self.delimiter = \".\"\n    @prefix = nil\n    @batch_size = 10\n    @batch_byte_size = nil\n    @flush_interval = nil\n    @postfix = nil\n    @socket = nil\n    @protocol = protocol || :udp\n    @s_mu = Mutex.new\n    connect\n  end\n\n  # @attribute [w] namespace\n  #   Writes are not thread safe.\n  def namespace=(namespace)\n    @namespace = namespace\n    @prefix = \"#{namespace}.\"\n  end\n\n  # @attribute [w] postfix\n  #   A value to be appended to the stat name after a '.'. If the value is\n  #   blank then the postfix will be reset to nil (rather than to '.').\n  def postfix=(pf)\n    case pf\n    when nil, false, '' then @postfix = nil\n    else @postfix = \".#{pf}\"\n    end\n  end\n\n  # @attribute [w] host\n  #   Writes are not thread safe.\n  #   Users should call hup after making changes.\n  def host=(host)\n    @host = host || '127.0.0.1'\n  end\n\n  # @attribute [w] port\n  #   Writes are not thread safe.\n  #   Users should call hup after making changes.\n  def port=(port)\n    @port = port || 8125\n  end\n\n  # @attribute [w] stat_delimiter\n  #   Allows for custom delimiter replacement for :: when Ruby modules are transformed to statsd metric name\n  def delimiter=(delimiter)\n    @delimiter = delimiter || \".\"\n  end\n\n  # Sends an increment (count = 1) for the given stat to the statsd server.\n  #\n  # @param [String] stat stat name\n  # @param [Numeric] sample_rate sample rate, 1 for always\n  # @see #count\n  def increment(stat, sample_rate=1)\n    count stat, 1, sample_rate\n  end\n\n  # Sends a decrement (count = -1) for the given stat to the statsd server.\n  #\n  # @param [String] stat stat name\n  # @param [Numeric] sample_rate sample rate, 1 for always\n  # @see #count\n  def decrement(stat, sample_rate=1)\n    count stat, -1, sample_rate\n  end\n\n  # Sends an arbitrary count for the given stat to the statsd server.\n  #\n  # @param [String] stat stat name\n  # @param [Integer] count count\n  # @param [Numeric] sample_rate sample rate, 1 for always\n  def count(stat, count, sample_rate=1)\n    send_stats stat, count, :c, sample_rate\n  end\n\n  # Sends an arbitary gauge value for the given stat to the statsd server.\n  #\n  # This is useful for recording things like available disk space,\n  # memory usage, and the like, which have different semantics than\n  # counters.\n  #\n  # @param [String] stat stat name.\n  # @param [Numeric] value gauge value.\n  # @param [Numeric] sample_rate sample rate, 1 for always\n  # @example Report the current user count:\n  #   $statsd.gauge('user.count', User.count)\n  def gauge(stat, value, sample_rate=1)\n    send_stats stat, value, :g, sample_rate\n  end\n\n  # Sends an arbitary set value for the given stat to the statsd server.\n  #\n  # This is for recording counts of unique events, which are useful to\n  # see on graphs to correlate to other values.  For example, a deployment\n  # might get recorded as a set, and be drawn as annotations on a CPU history\n  # graph.\n  #\n  # @param [String] stat stat name.\n  # @param [Numeric] value event value.\n  # @param [Numeric] sample_rate sample rate, 1 for always\n  # @example Report a deployment happening:\n  #   $statsd.set('deployment', DEPLOYMENT_EVENT_CODE)\n  def set(stat, value, sample_rate=1)\n    send_stats stat, value, :s, sample_rate\n  end\n\n  # Sends a timing (in ms) for the given stat to the statsd server. The\n  # sample_rate determines what percentage of the time this report is sent. The\n  # statsd server then uses the sample_rate to correctly track the average\n  # timing for the stat.\n  #\n  # @param [String] stat stat name\n  # @param [Integer] ms timing in milliseconds\n  # @param [Numeric] sample_rate sample rate, 1 for always\n  def timing(stat, ms, sample_rate=1)\n    send_stats stat, ms, :ms, sample_rate\n  end\n\n  # Reports execution time of the provided block using {#timing}.\n  #\n  # @param [String] stat stat name\n  # @param [Numeric] sample_rate sample rate, 1 for always\n  # @yield The operation to be timed\n  # @see #timing\n  # @example Report the time (in ms) taken to activate an account\n  #   $statsd.time('account.activate') { @account.activate! }\n  def time(stat, sample_rate=1)\n    start = MonotonicTime.time_in_ms\n    result = yield\n  ensure\n    timing(stat, (MonotonicTime.time_in_ms - start).round, sample_rate)\n    result\n  end\n\n  # Creates and yields a Batch that can be used to batch instrument reports into\n  # larger packets. Batches are sent either when the packet is \"full\" (defined\n  # by batch_size), or when the block completes, whichever is the sooner.\n  #\n  # @yield [Batch] a statsd subclass that collects and batches instruments\n  # @example Batch two instument operations:\n  #   $statsd.batch do |batch|\n  #     batch.increment 'sys.requests'\n  #     batch.gauge('user.count', User.count)\n  #   end\n  def batch(&block)\n    Batch.new(self).easy(&block)\n  end\n\n  # Reconnects the socket, useful if the address of the statsd has changed. This\n  # method is not thread safe from a perspective of stat submission. It is safe\n  # from resource leaks. Users do not normally need to call this, but calling it\n  # may be appropriate when reconfiguring a process (e.g. from HUP).\n  def connect\n    @s_mu.synchronize do\n      begin\n        @socket.close if @socket\n      rescue\n        # Errors are ignored on reconnects.\n      end\n\n      case @protocol\n      when :tcp\n        @socket = TCPSocket.new @host, @port\n      else\n        @socket = UDPSocket.new Addrinfo.ip(@host).afamily\n        @socket.connect host, port\n      end\n    end\n  end\n\n  protected\n\n  def send_to_socket(message)\n    self.class.logger.debug { \"Statsd: #{message}\" } if self.class.logger\n\n    retries = 0\n    n = 0\n    while true\n      # send(2) is atomic, however, in stream cases (TCP) the socket is left\n      # in an inconsistent state if a partial message is written. If that case\n      # occurs, the socket is closed down and we retry on a new socket.\n      message = @protocol == :tcp ? message + \"\\n\" : message\n      n = socket.write(message) rescue (err = $!; 0)\n      if n == message.length\n        break\n      end\n\n      connect\n      retries += 1\n      raise (err || \"statsd: Failed to send after #{retries} attempts\") if retries >= 5\n    end\n    n\n  rescue => boom\n    self.class.logger.error { \"Statsd: #{boom.class} #{boom}\" } if self.class.logger\n    nil\n  end\n\n  private\n\n  def send_stats(stat, delta, type, sample_rate=1)\n    if sample_rate == 1 or rand < sample_rate\n      # Replace Ruby module scoping with '.' and reserved chars (: | @) with underscores.\n      stat = stat.to_s.gsub('::', delimiter).tr(':|@', '_')\n      rate = \"|@#{sample_rate}\" unless sample_rate == 1\n      send_to_socket \"#{prefix}#{stat}#{postfix}:#{delta}|#{type}#{rate}\"\n    end\n  end\n\n  def socket\n    # Subtle: If the socket is half-way through initialization in connect, it\n    # cannot be used yet.\n    @s_mu.synchronize { @socket } || raise(ThreadError, \"socket missing\")\n  end\nend\n"
  },
  {
    "path": "spec/helper.rb",
    "content": "require 'bundler/setup'\n\nrequire 'simplecov'\nSimpleCov.start\n\nrequire 'minitest/autorun'\nrequire 'statsd'\nrequire 'logger'\nrequire 'timeout'\n\nclass FakeUDPSocket\n  def initialize\n    @buffer = []\n  end\n\n  def write(message)\n    @buffer.push [message]\n    message.length\n  end\n\n  def recv\n    @buffer.shift\n  end\n\n  def clear\n    @buffer = []\n  end\n\n  def to_s\n    inspect\n  end\n\n  def inspect\n    \"<#{self.class.name}: #{@buffer.inspect}>\"\n  end\nend\n\nclass FakeTCPSocket < FakeUDPSocket\n  alias_method :readline, :recv\n  def write(message)\n    @buffer.push message\n  end\nend\n"
  },
  {
    "path": "spec/statsd_admin_spec.rb",
    "content": "require 'helper'\n\ndescribe Statsd::Admin do\n\n  before do\n    class Statsd::Admin\n      o, $VERBOSE = $VERBOSE, nil\n      alias connect_old connect\n      def connect\n        $connect_count ||= 0\n        $connect_count += 1\n      end\n      $VERBOSE = o\n    end\n    @admin = Statsd::Admin.new('localhost', 1234)\n    @socket = @admin.instance_variable_set(:@socket, FakeTCPSocket.new)\n  end\n\n  after do\n    class Statsd::Admin\n      o, $VERBOSE = $VERBOSE, nil\n      alias connect connect_old\n      $VERBOSE = o\n    end\n  end\n\n  describe \"#initialize\" do\n    it \"should set the host and port\" do\n      _(@admin.host).must_equal 'localhost'\n      _(@admin.port).must_equal 1234\n    end\n\n    it \"should default the host to 127.0.0.1 and port to 8126\" do\n      statsd = Statsd::Admin.new\n      _(statsd.host).must_equal '127.0.0.1'\n      _(statsd.port).must_equal 8126\n    end\n  end\n\n  describe \"#host and #port\" do\n    it \"should set host and port\" do\n      @admin.host = '1.2.3.4'\n      @admin.port = 5678\n      _(@admin.host).must_equal '1.2.3.4'\n      _(@admin.port).must_equal 5678\n    end\n\n    it \"should not resolve hostnames to IPs\" do\n      @admin.host = 'localhost'\n      _(@admin.host).must_equal 'localhost'\n    end\n\n    it \"should set nil host to default\" do\n      @admin.host = nil\n      _(@admin.host).must_equal '127.0.0.1'\n    end\n\n    it \"should set nil port to default\" do\n      @admin.port = nil\n      _(@admin.port).must_equal 8126\n    end\n  end\n\n  %w(gauges counters timers).each do |action|\n    describe \"##{action}\" do\n      it \"should send a command and return a Hash\" do\n        [\"{'foo.bar': 0,\\n\",\n          \"'foo.baz': 1,\\n\",\n          \"'foo.quux': 2 }\\n\",\n          \"END\\n\",\"\\n\"].each do |line|\n          @socket.write line\n        end\n        result = @admin.send action.to_sym\n        _(result).must_be_kind_of Hash\n        _(result.size).must_equal 3\n        _(@socket.readline).must_equal \"#{action}\\n\"\n      end\n    end\n\n    describe \"#del#{action}\" do\n      it \"should send a command and return an Array\" do\n        [\"deleted: foo.bar\\n\",\n         \"deleted: foo.baz\\n\",\n         \"deleted: foo.quux\\n\",\n          \"END\\n\", \"\\n\"].each do |line|\n          @socket.write line\n        end\n        result = @admin.send \"del#{action}\", \"foo.*\"\n        _(result).must_be_kind_of Array\n        _(result.size).must_equal 3\n        _(@socket.readline).must_equal \"del#{action} foo.*\\n\"\n      end\n    end\n  end\n\n  describe \"#stats\" do\n    it \"should send a command and return a Hash\" do\n      [\"whatever: 0\\n\", \"END\\n\", \"\\n\"].each do |line| \n        @socket.write line\n      end\n      result = @admin.stats\n      _(result).must_be_kind_of Hash\n      _(result[\"whatever\"]).must_equal 0\n      _(@socket.readline).must_equal \"stats\\n\"\n    end\n  end\n\n  describe \"#connect\" do\n    it \"should reconnect\" do\n      c = $connect_count\n      @admin.connect\n      _(($connect_count - c)).must_equal 1\n    end\n  end\nend\n\n\n"
  },
  {
    "path": "spec/statsd_spec.rb",
    "content": "require 'helper'\n\ndescribe Statsd do\n  before do\n    class Statsd\n      o, $VERBOSE = $VERBOSE, nil\n      alias connect_old connect\n      def connect\n        $connect_count ||= 1\n        $connect_count += 1\n      end\n      $VERBOSE = o\n    end\n\n    @statsd = Statsd.new('localhost', 1234)\n    @socket = @statsd.instance_variable_set(:@socket, FakeUDPSocket.new)\n  end\n\n  after do\n    class Statsd\n      o, $VERBOSE = $VERBOSE, nil\n      alias connect connect_old\n      $VERBOSE = o\n    end\n  end\n\n  describe \"#initialize\" do\n    it \"should set the host and port\" do\n      _(@statsd.host).must_equal 'localhost'\n      _(@statsd.port).must_equal 1234\n    end\n\n    it \"should default the host to 127.0.0.1 and port to 8125\" do\n      statsd = Statsd.new\n      _(statsd.host).must_equal '127.0.0.1'\n      _(statsd.port).must_equal 8125\n    end\n\n    it \"should set delimiter to period by default\" do\n      _(@statsd.delimiter).must_equal \".\"\n    end\n  end\n\n  describe \"#host and #port\" do\n    it \"should set host and port\" do\n      @statsd.host = '1.2.3.4'\n      @statsd.port = 5678\n      _(@statsd.host).must_equal '1.2.3.4'\n      _(@statsd.port).must_equal 5678\n    end\n\n    it \"should not resolve hostnames to IPs\" do\n      @statsd.host = 'localhost'\n      _(@statsd.host).must_equal 'localhost'\n    end\n\n    it \"should set nil host to default\" do\n      @statsd.host = nil\n      _(@statsd.host).must_equal '127.0.0.1'\n    end\n\n    it \"should set nil port to default\" do\n      @statsd.port = nil\n      _(@statsd.port).must_equal 8125\n    end\n\n    it \"should allow an IPv6 address\" do\n      @statsd.host = '::1'\n      _(@statsd.host).must_equal '::1'\n    end\n  end\n\n  describe \"#delimiter\" do\n    it \"should set delimiter\" do\n      @statsd.delimiter = \"-\"\n      _(@statsd.delimiter).must_equal \"-\"\n    end\n\n    it \"should set default to period if not given a value\" do\n      @statsd.delimiter = nil\n      _(@statsd.delimiter).must_equal \".\"\n    end\n  end\n\n  describe \"#increment\" do\n    it \"should format the message according to the statsd spec\" do\n      @statsd.increment('foobar')\n      _(@socket.recv).must_equal ['foobar:1|c']\n    end\n\n    describe \"with a sample rate\" do\n      before { class << @statsd; def rand; 0; end; end } # ensure delivery\n      it \"should format the message according to the statsd spec\" do\n        @statsd.increment('foobar', 0.5)\n        _(@socket.recv).must_equal ['foobar:1|c|@0.5']\n      end\n    end\n  end\n\n  describe \"#decrement\" do\n    it \"should format the message according to the statsd spec\" do\n      @statsd.decrement('foobar')\n      _(@socket.recv).must_equal ['foobar:-1|c']\n    end\n\n    describe \"with a sample rate\" do\n      before { class << @statsd; def rand; 0; end; end } # ensure delivery\n      it \"should format the message according to the statsd spec\" do\n        @statsd.decrement('foobar', 0.5)\n        _(@socket.recv).must_equal ['foobar:-1|c|@0.5']\n      end\n    end\n  end\n\n  describe \"#gauge\" do\n    it \"should send a message with a 'g' type, per the nearbuy fork\" do\n      @statsd.gauge('begrutten-suffusion', 536)\n      _(@socket.recv).must_equal ['begrutten-suffusion:536|g']\n      @statsd.gauge('begrutten-suffusion', -107.3)\n      _(@socket.recv).must_equal ['begrutten-suffusion:-107.3|g']\n    end\n\n    describe \"with a sample rate\" do\n      before { class << @statsd; def rand; 0; end; end } # ensure delivery\n      it \"should format the message according to the statsd spec\" do\n        @statsd.gauge('begrutten-suffusion', 536, 0.1)\n        _(@socket.recv).must_equal ['begrutten-suffusion:536|g|@0.1']\n      end\n    end\n  end\n\n  describe \"#timing\" do\n    it \"should format the message according to the statsd spec\" do\n      @statsd.timing('foobar', 500)\n      _(@socket.recv).must_equal ['foobar:500|ms']\n    end\n\n    describe \"with a sample rate\" do\n      before { class << @statsd; def rand; 0; end; end } # ensure delivery\n      it \"should format the message according to the statsd spec\" do\n        @statsd.timing('foobar', 500, 0.5)\n        _(@socket.recv).must_equal ['foobar:500|ms|@0.5']\n      end\n    end\n  end\n\n  describe \"#set\" do\n    it \"should format the message according to the statsd spec\" do\n      @statsd.set('foobar', 765)\n      _(@socket.recv).must_equal ['foobar:765|s']\n    end\n\n    describe \"with a sample rate\" do\n      before { class << @statsd; def rand; 0; end; end } # ensure delivery\n      it \"should format the message according to the statsd spec\" do\n        @statsd.set('foobar', 500, 0.5)\n        _(@socket.recv).must_equal ['foobar:500|s|@0.5']\n      end\n    end\n  end\n\n  describe \"#time\" do\n    it \"should format the message according to the statsd spec\" do\n      @statsd.time('foobar') { 'test' }\n      _(@socket.recv).must_equal ['foobar:0|ms']\n    end\n\n    it \"should return the result of the block\" do\n      result = @statsd.time('foobar') { 'test' }\n      _(result).must_equal 'test'\n    end\n\n    describe \"when given a block with an explicit return\" do\n      it \"should format the message according to the statsd spec\" do\n        lambda { @statsd.time('foobar') { return 'test' } }.call\n        _(@socket.recv).must_equal ['foobar:0|ms']\n      end\n\n      it \"should return the result of the block\" do\n        result = lambda { @statsd.time('foobar') { return 'test' } }.call\n        _(result).must_equal 'test'\n      end\n    end\n\n    describe \"with a sample rate\" do\n      before { class << @statsd; def rand; 0; end; end } # ensure delivery\n\n      it \"should format the message according to the statsd spec\" do\n        @statsd.time('foobar', 0.5) { 'test' }\n        _(@socket.recv).must_equal ['foobar:0|ms|@0.5']\n      end\n    end\n  end\n\n  describe \"#sampled\" do\n    describe \"when the sample rate is 1\" do\n      before { class << @statsd; def rand; raise end; end }\n      it \"should send\" do\n        @statsd.timing('foobar', 500, 1)\n        _(@socket.recv).must_equal ['foobar:500|ms']\n      end\n    end\n\n    describe \"when the sample rate is greater than a random value [0,1]\" do\n      before { class << @statsd; def rand; 0; end; end } # ensure delivery\n      it \"should send\" do\n        @statsd.timing('foobar', 500, 0.5)\n        _(@socket.recv).must_equal ['foobar:500|ms|@0.5']\n      end\n    end\n\n    describe \"when the sample rate is less than a random value [0,1]\" do\n      before { class << @statsd; def rand; 1; end; end } # ensure no delivery\n      it \"should not send\" do\n        assert_nil @statsd.timing('foobar', 500, 0.5)\n      end\n    end\n\n    describe \"when the sample rate is equal to a random value [0,1]\" do\n      before { class << @statsd; def rand; 0; end; end } # ensure delivery\n      it \"should send\" do\n        @statsd.timing('foobar', 500, 0.5)\n        _(@socket.recv).must_equal ['foobar:500|ms|@0.5']\n      end\n    end\n  end\n\n  describe \"with namespace\" do\n    before { @statsd.namespace = 'service' }\n\n    it \"should add namespace to increment\" do\n      @statsd.increment('foobar')\n      _(@socket.recv).must_equal ['service.foobar:1|c']\n    end\n\n    it \"should add namespace to decrement\" do\n      @statsd.decrement('foobar')\n      _(@socket.recv).must_equal ['service.foobar:-1|c']\n    end\n\n    it \"should add namespace to timing\" do\n      @statsd.timing('foobar', 500)\n      _(@socket.recv).must_equal ['service.foobar:500|ms']\n    end\n\n    it \"should add namespace to gauge\" do\n      @statsd.gauge('foobar', 500)\n      _(@socket.recv).must_equal ['service.foobar:500|g']\n    end\n  end\n\n  describe \"with postfix\" do\n    before { @statsd.postfix = 'ip-23-45-56-78' }\n\n    it \"should add postfix to increment\" do\n      @statsd.increment('foobar')\n      _(@socket.recv).must_equal ['foobar.ip-23-45-56-78:1|c']\n    end\n\n    it \"should add postfix to decrement\" do\n      @statsd.decrement('foobar')\n      _(@socket.recv).must_equal ['foobar.ip-23-45-56-78:-1|c']\n    end\n\n    it \"should add namespace to timing\" do\n      @statsd.timing('foobar', 500)\n      _(@socket.recv).must_equal ['foobar.ip-23-45-56-78:500|ms']\n    end\n\n    it \"should add namespace to gauge\" do\n      @statsd.gauge('foobar', 500)\n      _(@socket.recv).must_equal ['foobar.ip-23-45-56-78:500|g']\n    end\n  end\n\n  describe '#postfix=' do\n    describe \"when nil, false, or empty\" do\n      it \"should set postfix to nil\" do\n        [nil, false, ''].each do |value|\n          @statsd.postfix = 'a postfix'\n          @statsd.postfix = value\n          assert_nil @statsd.postfix\n        end\n      end\n    end\n  end\n\n  describe \"with logging\" do\n    require 'stringio'\n    before { Statsd.logger = Logger.new(@log = StringIO.new)}\n\n    it \"should write to the log in debug\" do\n      Statsd.logger.level = Logger::DEBUG\n\n      @statsd.increment('foobar')\n\n      _(@log.string).must_match \"Statsd: foobar:1|c\"\n    end\n\n    it \"should not write to the log unless debug\" do\n      Statsd.logger.level = Logger::INFO\n\n      @statsd.increment('foobar')\n\n      _(@log.string).must_be_empty\n    end\n  end\n\n  describe \"stat names\" do\n    it \"should accept anything as stat\" do\n      @statsd.increment(Object, 1)\n    end\n\n    it \"should replace ruby constant delimeter with graphite package name\" do\n      class Statsd::SomeClass; end\n      @statsd.increment(Statsd::SomeClass, 1)\n\n      _(@socket.recv).must_equal ['Statsd.SomeClass:1|c']\n    end\n\n    describe \"custom delimiter\" do\n      before do\n        @statsd.delimiter = \"-\"\n      end\n\n      it \"should replace ruby constant delimiter with custom delimiter\" do\n        class Statsd::SomeOtherClass; end\n        @statsd.increment(Statsd::SomeOtherClass, 1)\n\n        _(@socket.recv).must_equal ['Statsd-SomeOtherClass:1|c']\n      end\n    end\n\n    it \"should replace statsd reserved chars in the stat name\" do\n      @statsd.increment('ray@hostname.blah|blah.blah:blah', 1)\n      _(@socket.recv).must_equal ['ray_hostname.blah_blah.blah_blah:1|c']\n    end\n  end\n\n  describe \"handling socket errors\" do\n    before do\n      require 'stringio'\n      Statsd.logger = Logger.new(@log = StringIO.new)\n      @socket.instance_variable_set(:@err_count, 0)\n      @socket.instance_eval { def write(*) @err_count+=1; raise SocketError end }\n    end\n\n    it \"should ignore socket errors\" do\n      assert_nil @statsd.increment('foobar')\n    end\n\n    it \"should log socket errors\" do\n      @statsd.increment('foobar')\n      _(@log.string).must_match 'Statsd: SocketError'\n    end\n\n    it \"should retry and reconnect on socket errors\" do\n      $connect_count = 0\n      @statsd.increment('foobar')\n      _(@socket.instance_variable_get(:@err_count)).must_equal 5\n      _($connect_count).must_equal 5\n    end\n  end\n\n  describe \"batching\" do\n    it \"should have a default batch size of 10\" do\n      _(@statsd.batch_size).must_equal 10\n    end\n\n    it \"should have a default batch byte size of nil\" do\n      assert_nil @statsd.batch_byte_size\n    end\n\n    it \"should have a default flush interval of nil\" do\n      assert_nil @statsd.flush_interval\n    end\n\n    it \"should have a modifiable batch size\" do\n      @statsd.batch_size = 7\n      _(@statsd.batch_size).must_equal 7\n      @statsd.batch do |b|\n        _(b.batch_size).must_equal 7\n      end\n\n      @statsd.batch_size = nil\n      @statsd.batch_byte_size = 1472\n      @statsd.batch do |b|\n        assert_nil b.batch_size\n        _(b.batch_byte_size).must_equal 1472\n      end\n\n    end\n\n    it 'should have a modifiable flush interval' do\n      @statsd.flush_interval = 1\n      _(@statsd.flush_interval).must_equal 1\n      @statsd.batch do |b|\n        _(b.flush_interval).must_equal 1\n      end\n    end\n\n    it \"should flush the batch at the batch size or at the end of the block\" do\n      @statsd.batch do |b|\n        b.batch_size = 3\n\n        # The first three should flush, the next two will be flushed when the\n        # block is done.\n        5.times { b.increment('foobar') }\n\n        _(@socket.recv).must_equal [([\"foobar:1|c\"] * 3).join(\"\\n\")]\n      end\n\n      _(@socket.recv).must_equal [([\"foobar:1|c\"] * 2).join(\"\\n\")]\n    end\n\n    it \"should flush based on batch byte size\" do\n      @statsd.batch do |b|\n        b.batch_size = nil\n        b.batch_byte_size = 22\n\n        # The first two should flush, the last will be flushed when the\n        # block is done.\n        3.times { b.increment('foobar') }\n\n        _(@socket.recv).must_equal [([\"foobar:1|c\"] * 2).join(\"\\n\")]\n      end\n\n      _(@socket.recv).must_equal [\"foobar:1|c\"]\n    end\n\n    it \"should flush immediately when the queue is exactly a batch size\" do\n      @statsd.batch do |b|\n        b.batch_size = nil\n        b.batch_byte_size = 21\n\n        # The first two should flush together\n        2.times { b.increment('foobar') }\n\n        _(@socket.recv).must_equal [([\"foobar:1|c\"] * 2).join(\"\\n\")]\n      end\n    end\n\n    it \"should flush when the interval has passed\" do\n      @statsd.batch do |b|\n        b.batch_size = nil\n        b.flush_interval = 0.01\n\n        # The first two should flush, the last will be flushed when the\n        # block is done.\n        2.times { b.increment('foobar') }\n        sleep(0.03)\n        b.increment('foobar')\n\n        _(@socket.recv).must_equal [([\"foobar:1|c\"] * 2).join(\"\\n\")]\n      end\n\n      _(@socket.recv).must_equal [\"foobar:1|c\"]\n    end\n\n    it \"should not flush to the socket if the backlog is empty\" do\n      batch = Statsd::Batch.new(@statsd)\n      batch.flush\n      _(@socket.recv).must_be :nil?\n\n      batch.increment 'foobar'\n      batch.flush\n      _(@socket.recv).must_equal %w[foobar:1|c]\n    end\n\n    it \"should support setting namespace for the underlying instance\" do\n      batch = Statsd::Batch.new(@statsd)\n      batch.namespace = 'ns'\n      _(@statsd.namespace).must_equal 'ns'\n    end\n\n    it \"should support setting host for the underlying instance\" do\n      batch = Statsd::Batch.new(@statsd)\n      batch.host = '1.2.3.4'\n      _(@statsd.host).must_equal '1.2.3.4'\n    end\n\n    it \"should support setting port for the underlying instance\" do\n      batch = Statsd::Batch.new(@statsd)\n      batch.port = 42\n      _(@statsd.port).must_equal 42\n    end\n\n  end\n\n  describe \"#connect\" do\n    it \"should reconnect\" do\n      c = $connect_count\n      @statsd.connect\n      _(($connect_count - c)).must_equal 1\n    end\n  end\n\nend\n\ndescribe Statsd do\n  describe \"with a real UDP socket\" do\n    it \"should actually send stuff over the socket\" do\n      family = Addrinfo.udp(UDPSocket.getaddress('localhost'), 0).afamily\n      begin\n        socket = UDPSocket.new family\n        host, port = 'localhost', 0\n        socket.bind(host, port)\n        port = socket.addr[1]\n\n        statsd = Statsd.new(host, port)\n        statsd.increment('foobar')\n        message = socket.recvfrom(16).first\n        _(message).must_equal 'foobar:1|c'\n      ensure\n        socket.close\n      end\n    end\n\n    it \"should send stuff over an IPv4 socket\" do\n      begin\n        socket = UDPSocket.new Socket::AF_INET\n        host, port = '127.0.0.1', 0\n        socket.bind(host, port)\n        port = socket.addr[1]\n\n        statsd = Statsd.new(host, port)\n        statsd.increment('foobar')\n        message = socket.recvfrom(16).first\n        _(message).must_equal 'foobar:1|c'\n      ensure\n        socket.close\n      end\n    end\n\n    it \"should send stuff over an IPv6 socket\" do\n      begin\n        socket = UDPSocket.new Socket::AF_INET6\n        host, port = '::1', 0\n        socket.bind(host, port)\n        port = socket.addr[1]\n\n        statsd = Statsd.new(host, port)\n        statsd.increment('foobar')\n        message = socket.recvfrom(16).first\n        _(message).must_equal 'foobar:1|c'\n      ensure\n        socket.close\n      end\n    end\n  end\n\n  describe \"supports TCP sockets\" do\n    it \"should connect to and send stats over TCPv4\" do\n      begin\n        host, port = '127.0.0.1', 0\n        server = TCPServer.new host, port\n        port = server.addr[1]\n\n        socket = nil\n        Thread.new { socket = server.accept }\n\n        statsd = Statsd.new(host, port, :tcp)\n        statsd.increment('foobar')\n\n        Timeout.timeout(5) do\n          Thread.pass while socket == nil\n        end\n\n        message = socket.recvfrom(16).first\n        _(message).must_equal \"foobar:1|c\\n\"\n      ensure\n        socket.close if socket\n        server.close\n      end\n    end\n\n    it \"should connect to and send stats over TCPv6\" do\n      begin\n        host, port = '::1', 0\n        server = TCPServer.new host, port\n        port = server.addr[1]\n\n        socket = nil\n        Thread.new { socket = server.accept }\n\n        statsd = Statsd.new(host, port, :tcp)\n        statsd.increment('foobar')\n\n        Timeout.timeout(5) do\n          Thread.pass while socket == nil\n        end\n\n        message = socket.recvfrom(16).first\n        _(message).must_equal \"foobar:1|c\\n\"\n      ensure\n        socket.close if socket\n        server.close\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "statsd-ruby.gemspec",
    "content": "# -*- encoding: utf-8 -*-\n\nGem::Specification.new(\"statsd-ruby\", \"1.5.0\") do |s|\n  s.authors =  `git log --format='%aN' | sort -u`.split(\"\\n\")\n  s.email = \"reinh@reinh.com\"\n\n  s.summary = \"A Ruby StatsD client\"\n  s.description = \"A Ruby StatsD client (https://github.com/etsy/statsd)\"\n\n  s.homepage = \"https://github.com/reinh/statsd\"\n  s.licenses = %w[MIT]\n\n  s.extra_rdoc_files = %w[LICENSE.txt README.rdoc]\n\n  if $0 =~ /gem/ # If running under rubygems (building), otherwise, just leave\n    s.files         = `git ls-files`.split($\\)\n    s.test_files    = s.files.grep(%r{^(test|spec|features)/})\n  end\n\n  s.add_development_dependency \"minitest\", \">= 5.6.0\"\n  s.add_development_dependency \"yard\"\n  s.add_development_dependency \"simplecov\", \">= 0.6.4\"\n  s.add_development_dependency \"rake\"\nend\n\n"
  }
]