lib/statsd/instrument.rb in statsd-instrument-1.5.0 vs lib/statsd/instrument.rb in statsd-instrument-1.6.0

- old
+ new

@@ -1,38 +1,46 @@ require 'socket' require 'benchmark' -require 'timeout' -class << Benchmark - def ms - 1000 * realtime { yield } - end -end +require 'statsd/instrument/version' - module StatsD class << self attr_accessor :host, :port, :mode, :logger, :enabled, :default_sample_rate, :prefix, :implementation end self.enabled = true self.default_sample_rate = 1.0 self.implementation = :statsd - TimeoutClass = defined?(::SystemTimer) ? ::SystemTimer : ::Timeout - # StatsD.server = 'localhost:1234' def self.server=(conn) self.host, port = conn.split(':') self.port = port.to_i + invalidate_socket end + def self.host=(host) + @host = host + invalidate_socket + end + + def self.port=(port) + @port = port + invalidate_socket + end + module Instrument + + 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) add_to_method(method, name, :measure) do |old_method, new_method, metric_name, *args| define_method(new_method) do |*args, &block| - StatsD.measure(metric_name.respond_to?(:call) ? metric_name.call(self, args) : metric_name, nil, sample_rate) { send(old_method, *args, &block) } + StatsD.measure(StatsD::Instrument.generate_metric_name(metric_name, self, *args), nil, sample_rate) { send(old_method, *args, &block) } end end end def statsd_count_success(method, name, sample_rate = StatsD.default_sample_rate) @@ -45,14 +53,12 @@ raise else truthiness = (yield(result) rescue false) if block_given? result ensure - result = truthiness == false ? 'failure' : 'success' - key = metric_name.respond_to?(:call) ? metric_name.call(self, args) : metric_name - - StatsD.increment("#{key}.#{result}", sample_rate) + suffix = truthiness == false ? 'failure' : 'success' + StatsD.increment("#{StatsD::Instrument.generate_metric_name(metric_name, self, *args)}.#{suffix}", 1, sample_rate) end end end end @@ -66,26 +72,43 @@ raise else truthiness = (yield(result) rescue false) if block_given? result ensure - StatsD.increment(metric_name.respond_to?(:call) ? metric_name.call(self, args) : metric_name, sample_rate) if truthiness + StatsD.increment(StatsD::Instrument.generate_metric_name(metric_name, self, *args), sample_rate) if truthiness end end end end def statsd_count(method, name, sample_rate = StatsD.default_sample_rate) add_to_method(method, name, :count) do |old_method, new_method, metric_name| define_method(new_method) do |*args, &block| - StatsD.increment(metric_name.respond_to?(:call) ? metric_name.call(self, args) : metric_name, sample_rate) + StatsD.increment(StatsD::Instrument.generate_metric_name(metric_name, self, *args), 1, sample_rate) send(old_method, *args, &block) end end end + def statsd_remove_count(method, name) + remove_from_method(method, name, :count) + end + + def statsd_remove_count_if(method, name) + remove_from_method(method, name, :count_if) + end + + def statsd_remove_count_success(method, name) + remove_from_method(method, name, :count_success) + end + + def statsd_remove_measure(method, name) + remove_from_method(method, name, :measure) + end + private + def add_to_method(method, name, action, &block) metric_name = name method_name_without_statsd = :"#{method}_for_#{action}_on_#{self.name}_without_#{name}" # raw_ssl_request_for_measure_on_FedEx_without_ActiveMerchant.Shipping.#{self.class.name}.ssl_request @@ -98,68 +121,107 @@ alias_method method_name_without_statsd, method yield method_name_without_statsd, method_name_with_statsd, metric_name alias_method method, method_name_with_statsd end + + def remove_from_method(method, name, action) + method_name_without_statsd = :"#{method}_for_#{action}_on_#{self.name}_without_#{name}" + method_name_with_statsd = :"#{method}_for_#{action}_on_#{self.name}_with_#{name}" + send(:remove_method, method_name_with_statsd) + alias_method method, method_name_without_statsd + send(:remove_method, method_name_without_statsd) + end end # glork:320|ms - def self.measure(key, milli = nil, sample_rate = default_sample_rate) + def self.measure(key, milli = nil, sample_rate = default_sample_rate, tags = nil) result = nil - ms = milli || Benchmark.ms do + ms = milli || 1000 * Benchmark.realtime do result = yield end - write(key, ms, :ms, sample_rate) + collect(key, ms, :ms, sample_rate, tags) result end # gorets:1|c - def self.increment(key, delta = 1, sample_rate = default_sample_rate) - write(key, delta, :incr, sample_rate) + def self.increment(key, delta = 1, sample_rate = default_sample_rate, tags = nil) + collect(key, delta, :incr, sample_rate, tags) end # gaugor:333|g # guagor:1234|kv|@1339864935 (statsite) - def self.gauge(key, value, sample_rate_or_epoch = default_sample_rate) - write(key, value, :g, sample_rate_or_epoch) + def self.gauge(key, value, sample_rate_or_epoch = default_sample_rate, tags = nil) + collect(key, value, :g, sample_rate_or_epoch, tags) 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) + end + private + def self.invalidate_socket + @socket = nil + end + def self.socket - @socket ||= UDPSocket.new + if @socket.nil? + @socket = UDPSocket.new + @socket.connect(host, port) + end + @socket end - def self.write(k,v,op, sample_rate = default_sample_rate) + def self.collect(k, v, op, sample_rate = default_sample_rate, tags = nil) return unless enabled return if sample_rate < 1 && rand > sample_rate - k = k.gsub('::', '.') + command = generate_packet(k, v, op, sample_rate, tags) + write_packet(command) + end + def self.write_packet(command) + if mode.to_s == 'production' + begin + socket.send(command, 0) + rescue SocketError, IOError, SystemCallError => e + logger.error e + end + else + logger.info "[StatsD] #{command}" + end + end + + def self.clean_tags(tags) + tags.map do |tag| + components = tag.split(':', 2) + components.map { |c| c.gsub(/[^\w\.-]+/, '_') }.join(':') + end + end + + def self.generate_packet(k, v, op, sample_rate = default_sample_rate, tags = nil) command = "#{self.prefix + '.' if self.prefix}#{k}:#{v}" case op when :incr command << '|c' when :ms command << '|ms' when :g command << (self.implementation == :statsite ? '|kv' : '|g') + when :h + raise NotImplementedError, "Histograms only supported on DataDog implementation." unless self.implementation == :datadog + command << '|h' end command << "|@#{sample_rate}" if sample_rate < 1 || (self.implementation == :statsite && sample_rate > 1) - command << "\n" if self.implementation == :statsite - - if mode.to_s == 'production' - socket_wrapper { socket.send(command, 0, host, port) } - else - logger.info "[StatsD] #{command}" + if tags + raise ArgumentError, "Tags are only supported on Datadog" unless self.implementation == :datadog + command << "|##{clean_tags(tags).join(',')}" end - end - def self.socket_wrapper(options = {}) - TimeoutClass.timeout(options.fetch(:timeout, 0.1)) { yield } - rescue Timeout::Error, SocketError, IOError, SystemCallError => e - logger.error e + command << "\n" if self.implementation == :statsite + return command end end -