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)

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_latency ⇒ Boolean

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.



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

def allow_measuring_latency
  false
end

.default_value ⇒ Numeric?

Returning nil makes value a required argument.

Returns:

  • (Numeric, nil) —

    The default value for the metric.

Raises:

  • (NotImplementedError)


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

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
85
# 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
    # Own a frozen name without freezing a mutable string from the caller.
    @name = -DatagramBlueprintBuilder.normalize_name(name)
    @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



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

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

.method_name ⇒ Symbol

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

Returns:

  • (Symbol) —

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

Raises:

  • (NotImplementedError)


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

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

.metric_name ⇒ String?

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).



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

def metric_name
  @name
end

.sample?(sample_rate) ⇒ Boolean

Returns:

  • (Boolean)


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

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

.sample_rate ⇒ Float

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

Returns:

  • (Float) —

    The defined sample rate for a metric class.

Raises:



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

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

  @sample_rate
end

.type ⇒ String

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

Returns:

  • (String) —

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

Raises:

  • (NotImplementedError)


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

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