Class: StatsD::Instrument::CompiledMetric

Inherits:
Object
  • Object
show all
Defined in:
lib/statsd/instrument/compiled_metric.rb

Overview

A compiled metric pre-builds the datagram template at definition time to minimize allocations during metric emission. This is particularly beneficial for high-frequency metrics with consistent tag patterns.

Example: class CheckoutMetric < StatsD::Instrument::CompiledMetric::Counter define( name: "checkout.completed", static_tags: { service: "web" }, tags: { shop_id: Integer, user_id: Integer } ) end

# Later, emit with minimal allocations: CheckoutMetric.increment(shop_id: 123, user_id: 456, value: 1)

Direct Known Subclasses

Counter, Distribution, Gauge

Defined Under Namespace

Classes: Counter, DatagramBlueprintBuilder, DefinitionError, Distribution, Gauge, PrecompiledDatagram

Constant Summary collapse

DEFAULT_MAX_TAG_COMBINATION_CACHE_SIZE =

Default maximum number of unique tag combinations to cache before clearing the cache to prevent unbounded memory growth

5000

Class Method Summary collapse

Class Method Details

.allow_measuring_latencyBoolean

The value kwarg will be ignored and instead the execution time of the block in milliseconds will be used. The return value of the block will be passed through.

Returns:

  • (Boolean)

    When set to true, the created method_name method will accept a block.



105
106
107
# File 'lib/statsd/instrument/compiled_metric.rb', line 105

def allow_measuring_latency
  false
end

.default_valueNumeric?

Returning nil makes value a required argument.

Returns:

  • (Numeric, nil)

    The default value for the metric.

Raises:

  • (NotImplementedError)


98
99
100
# File 'lib/statsd/instrument/compiled_metric.rb', line 98

def default_value
  raise NotImplementedError, "Subclasses must implement #default_value"
end

.define(name:, static_tags: {}, tags: {}, no_prefix: false, sample_rate: nil, max_cache_size: DEFAULT_MAX_TAG_COMBINATION_CACHE_SIZE) ⇒ Class

Defines a new compiled metric class with the given configuration.

Parameters:

  • name (String)

    The metric name

  • static_tags (Hash{Symbol, String => String, Integer, Float}) (defaults to: {})

    Tags with fixed values

  • tags (Hash{Symbol, String => Class}) (defaults to: {})

    Tags with dynamic values (Integer, Float, or String)

  • no_prefix (Boolean) (defaults to: false)

    If true, skip the StatsD prefix and default_tags

  • sample_rate (Float, nil) (defaults to: nil)

    The sample rate (0.0-1.0) for this metric, nil for no sampling

  • max_cache_size (Integer) (defaults to: DEFAULT_MAX_TAG_COMBINATION_CACHE_SIZE)

    Maximum tag combinations this metric supports, and will be retained in-memory. Cardinality beyond this number will fall back to the slow path and should be avoided.

Returns:

  • (Class)

    A new CompiledMetric subclass configured for this metric



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/statsd/instrument/compiled_metric.rb', line 41

def define(name:, static_tags: {}, tags: {}, no_prefix: false, sample_rate: nil, max_cache_size: DEFAULT_MAX_TAG_COMBINATION_CACHE_SIZE)
  if equal?(CompiledMetric) || superclass.equal?(CompiledMetric)
    raise DefinitionError,
      "`define` must be called on a subclass, not on #{self.name} directly. " \
        "Use `Class.new(#{self.name}) { define(...) }` or " \
        "`class MyMetric < #{self.name}; define(...); end` instead."
  end

  if defined?(@datagram_blueprint)
    raise DefinitionError,
      "`define` has already been called on #{self.name}. " \
        "Each CompiledMetric subclass can only be defined once."
  end

  client = StatsD.singleton_client

  # Build the datagram blueprint using the builder
  # The builder handles prefix, tags compilation, and blueprint construction
  datagram_blueprint = DatagramBlueprintBuilder.build(
    name: name,
    type: type,
    client_prefix: client.prefix,
    no_prefix: no_prefix,
    default_tags: client.default_tags,
    static_tags: static_tags,
    dynamic_tags: tags,
    sample_rate: sample_rate || client.default_sample_rate,
  )

  # Create a new class for this specific metric
  # Using classes instead of instances for better YJIT optimization
  metric_class = tap do
    @name = DatagramBlueprintBuilder.normalize_name(name).freeze
    @datagram_blueprint = datagram_blueprint
    @tag_combination_cache = {}
    @max_cache_size = max_cache_size
    @singleton_client = client
    @sample_rate = sample_rate || client.default_sample_rate

    define_metric_method(tags)
  end

  metric_class
end

.define_metric_method(tags) ⇒ Object

Defines the metric emission method - must be implemented by subclasses

Parameters:

  • tags (Hash)

    The dynamic tags configuration



111
112
113
114
115
116
117
# File 'lib/statsd/instrument/compiled_metric.rb', line 111

def define_metric_method(tags)
  if tags.any?
    define_dynamic_method(tags)
  else
    define_static_method
  end
end

.method_nameSymbol

Returns The method name to define (e.g., :increment).

Returns:

  • (Symbol)

    The method name to define (e.g., :increment)

Raises:

  • (NotImplementedError)


92
93
94
# File 'lib/statsd/instrument/compiled_metric.rb', line 92

def method_name
  raise NotImplementedError, "Subclasses must implement #method_name"
end

.metric_nameString?

Returns The normalized metric name for this compiled metric class (nil if not yet defined).

Returns:

  • (String, nil)

    The normalized metric name for this compiled metric class (nil if not yet defined).



124
125
126
# File 'lib/statsd/instrument/compiled_metric.rb', line 124

def metric_name
  @name
end

.sample?(sample_rate) ⇒ Boolean

Returns:

  • (Boolean)


119
120
121
# File 'lib/statsd/instrument/compiled_metric.rb', line 119

def sample?(sample_rate)
  @singleton_client.sink.sample?(sample_rate)
end

.sample_rateFloat

Will raise when define has not yet been called on the class.

Returns:

  • (Float)

    The defined sample rate for a metric class.

Raises:



130
131
132
133
134
# File 'lib/statsd/instrument/compiled_metric.rb', line 130

def sample_rate
  raise DefinitionError, "Every CompiledMetric subclass needs to call `define` before accessing its sample_rate." unless defined?(@sample_rate)

  @sample_rate
end

.typeString

Returns The metric type character (e.g., "c" for counter).

Returns:

  • (String)

    The metric type character (e.g., "c" for counter)

Raises:

  • (NotImplementedError)


87
88
89
# File 'lib/statsd/instrument/compiled_metric.rb', line 87

def type
  raise NotImplementedError, "Subclasses must implement #type"
end