# frozen_string_literal: true # The Metric class represents a metric sample to be send by a backend. # # @!attribute type # @return [Symbol] The metric type. Must be one of {StatsD::Instrument::Metric::TYPES} # @!attribute name # @return [String] The name of the metric. {StatsD#prefix} will automatically be applied # to the metric in the constructor, unless the :no_prefix option is set or is # overridden by the :prefix option. Note that :no_prefix has greater # precedence than :prefix. # @!attribute value # @see #default_value # @return [Numeric, String] The value to collect for the metric. Depending on the metric # type, value can be a string, integer, or float. # @!attribute sample_rate # The sample rate to use for the metric. How the sample rate is handled differs per backend. # The UDP backend will actually sample metric submissions based on the sample rate, while # the logger backend will just include the sample rate in its output for debugging purposes. # @see StatsD#default_sample_rate # @return [Float] The sample rate to use for this metric. This should be a value between # 0 and 1. If not set, it will use the default sample rate set to {StatsD#default_sample_rate}. # @!attribute tags # The tags to associate with the metric. # @note Only the Datadog implementation supports tags. # @see .normalize_tags # @return [Array, Hash, nil] the tags to associate with the metric. # You can either specify the tags as an array of strings, or a Hash of key/value pairs. # # @see StatsD The StatsD module contains methods that generate metric instances. # @see StatsD::Instrument::Backend A StatsD::Instrument::Backend is used to collect metrics. # class StatsD::Instrument::Metric unless Regexp.method_defined?(:match?) # for ruby 2.3 module RubyBackports refine Regexp do def match?(str) (self =~ str) != nil end end end using RubyBackports end def self.new( type:, name:, value: default_value(type), sample_rate: StatsD.default_sample_rate, tags: nil, metadata: nil ) # pass keyword arguments as positional arguments for performance reasons, # since MRI's C implementation of new turns keyword arguments into a hash super(type, name, value, sample_rate, tags, metadata) end # The default value for this metric, which will be used if it is not set. # # A default value is only defined for counter metrics (1). For all other # metric types, this emthod will raise an ArgumentError. # # # A default value is only defined for counter metrics (1). For all other # metric types, this emthod will raise an ArgumentError. # # @return [Numeric, String] The default value for this metric. # @raise ArgumentError if the metric type doesn't have a default value def self.default_value(type) case type when :c then 1 else raise ArgumentError, "A value is required for metric type #{type.inspect}." end end attr_accessor :type, :name, :value, :sample_rate, :tags, :metadata # Initializes a new metric instance. # Normally, you don't want to call this method directly, but use one of the metric collection # methods on the {StatsD} module. # # @param type [Symbol] The type of the metric. # @option name [String] :name The name of the metric without prefix. # @option value [Numeric, String, nil] The value to collect for the metric. # @option sample_rate [Numeric, nil] The sample rate to use. If not set, it will use # {StatsD#default_sample_rate}. # @option tags [Array, Hash, nil] :tags The tags to apply to this metric. # See {.normalize_tags} for more information. def initialize(type, name, value, sample_rate, tags, metadata) # rubocop:disable Metrics/ParameterLists raise ArgumentError, "Metric :type is required." unless type raise ArgumentError, "Metric :name is required." unless name raise ArgumentError, "Metric :value is required." unless value @type = type @name = normalize_name(name) @value = value @sample_rate = sample_rate @tags = StatsD::Instrument::Metric.normalize_tags(tags) if StatsD.default_tags @tags = Array(@tags) + StatsD.default_tags end @metadata = metadata end # @private # @return [String] def to_s str = +"#{TYPES[type]} #{name}:#{value}" str << " @#{sample_rate}" if sample_rate != 1.0 tags&.each { |tag| str << " ##{tag}" } str end # @private # @return [String] def inspect "#" end # The metric types that are supported by this library. Note that every StatsD server # implementation only supports a subset of them. TYPES = { c: 'increment', ms: 'measure', g: 'gauge', h: 'histogram', d: 'distribution', kv: 'key/value', s: 'set', } # Strip metric names of special characters used by StatsD line protocol, replace with underscore # # @param name [String] # @return [String] def normalize_name(name) # fast path when no normalization is needed to avoid copying the string return name unless /[:|@]/.match?(name) name.tr(':|@', '_') end # Utility function to convert tags to the canonical form. # # - Tags specified as key value pairs will be converted into an array # - Tags are normalized to only use word characters and underscores. # # @param tags [Array, Hash, nil] Tags specified in any form. # @return [Array, nil] the list of tags in canonical form. def self.normalize_tags(tags) return unless tags tags = tags.map { |k, v| k.to_s + ":" + v.to_s } if tags.is_a?(Hash) # fast path when no string replacement is needed return tags unless tags.any? { |tag| /[|,]/.match?(tag) } tags.map { |tag| tag.tr('|,', '') } end end