Class: Wurk::Metrics::Statsd

Inherits:
Object
  • Object
show all
Includes:
Wurk::Middleware::ServerMiddleware
Defined in:
lib/wurk/metrics/statsd.rb

Overview

Pro parity (§9): emits per-job timing + counters to a statsd / dogstatsd client. The client itself is plumbed in by the host app via:

Wurk.configure_server do |config|
  config.dogstatsd = -> { Datadog::Statsd.new('metrics.example.com', 8125) }
  config.server_middleware { |chain| chain.add Wurk::Metrics::Statsd }
end

The ‘dogstatsd` accessor is a callable — invoked once per process, memoized — so the client is built lazily AFTER fork. Sharing a UDP socket across forks is fine, but `Datadog::Statsd` keeps thread-locals that must be initialized inside the child.

Per-job tuning via ‘Statsd.options = ->(klass, job, queue) { sample_rate: }`. Default options: tags `[“worker:<klass>”, “queue:<q>”]`, sample_rate 1.0. The `dd_rate` job option, when present, overrides sample_rate.

Metric naming follows Sidekiq Pro 8+: every metric prefixed ‘sidekiq.` (the prefix is hardcoded, not configurable — third-party dashboards built for Sidekiq Pro work unchanged).

‘Statsd.increment(metric, tags:)` is the class-level fast path used by other Wurk components (Buffered client, Expiry middleware, super_fetch recovery, Batch lifecycle). No-op when no client is configured so callers never have to guard.

Spec: docs/target/sidekiq-pro.md §9.

Constant Summary collapse

METRIC_PREFIX =
'sidekiq.'
DEFAULT_SAMPLE_RATE =
1.0

Class Attribute Summary collapse

Attributes included from Wurk::Middleware::ServerMiddleware

#config

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Wurk::Middleware::ServerMiddleware

#logger, #redis, #redis_pool

Class Attribute Details

.optionsObject

Returns the value of attribute options.



39
40
41
# File 'lib/wurk/metrics/statsd.rb', line 39

def options
  @options
end

Class Method Details

.clientObject

Resolves the live client: invokes the configured ‘dogstatsd` proc exactly once per process and memoizes. Returns nil when no proc is configured, so callers get a clean no-op without raising.



94
95
96
97
98
99
100
101
# File 'lib/wurk/metrics/statsd.rb', line 94

def client
  return @client if defined?(@client) && !@client.nil?

  builder = Wurk.configuration.respond_to?(:dogstatsd) ? Wurk.configuration.dogstatsd : nil
  return nil if builder.nil?

  @client = builder.respond_to?(:call) ? builder.call : builder
end

.distribution(metric, value, tags: nil, sample_rate: DEFAULT_SAMPLE_RATE) ⇒ Object

Distribution send. Some statsd clients lack ‘distribution` (vanilla statsd-ruby, for example) — fall back to `histogram` so the metric still lands somewhere. `dogstatsd-ruby` always has `distribution`.



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/wurk/metrics/statsd.rb', line 73

def distribution(metric, value, tags: nil, sample_rate: DEFAULT_SAMPLE_RATE)
  client = self.client
  return nil unless client

  opts = sample_rate_kw(sample_rate)
  opts[:tags] = tags if tags
  name = "#{METRIC_PREFIX}#{metric}"
  if client.respond_to?(:distribution)
    client.distribution(name, value, **opts)
  elsif client.respond_to?(:histogram)
    client.histogram(name, value, **opts)
  end
  nil
rescue StandardError => e
  handle_error(e)
  nil
end

.gauge(metric, value, tags: nil, sample_rate: DEFAULT_SAMPLE_RATE) ⇒ Object



57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/wurk/metrics/statsd.rb', line 57

def gauge(metric, value, tags: nil, sample_rate: DEFAULT_SAMPLE_RATE)
  client = self.client
  return nil unless client

  opts = sample_rate_kw(sample_rate)
  opts[:tags] = tags if tags
  client.gauge("#{METRIC_PREFIX}#{metric}", value, **opts)
  nil
rescue StandardError => e
  handle_error(e)
  nil
end

.increment(metric, tags: nil, sample_rate: DEFAULT_SAMPLE_RATE) ⇒ Object

Counter shortcut used across the codebase. Tags are forwarded as given — caller’s job to namespace them (‘“class:Foo”`, `“queue:bar”`). No-op when no client is wired up.



44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/wurk/metrics/statsd.rb', line 44

def increment(metric, tags: nil, sample_rate: DEFAULT_SAMPLE_RATE)
  client = self.client
  return nil unless client

  opts = sample_rate_kw(sample_rate)
  opts[:tags] = tags if tags
  client.increment("#{METRIC_PREFIX}#{metric}", **opts)
  nil
rescue StandardError => e
  handle_error(e)
  nil
end

.reset!Object

Test/lifecycle hook. Reset between specs and after fork so the parent’s socket doesn’t bleed into children.



105
106
107
# File 'lib/wurk/metrics/statsd.rb', line 105

def reset!
  @client = nil
end

Instance Method Details

#call(_worker, job, queue) ⇒ Object

rubocop:disable Metrics/AbcSize



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/wurk/metrics/statsd.rb', line 120

def call(_worker, job, queue) # rubocop:disable Metrics/AbcSize
  client = safe_client
  return yield if client.nil?

  klass = job['class']
  opts  = per_job_options(klass, job, queue)
  tags  = opts[:tags]
  rate  = opts.fetch(:sample_rate, DEFAULT_SAMPLE_RATE)

  emit(:increment, 'jobs.count', tags: tags, sample_rate: rate)
  started = monotonic_ms
  success = false
  begin
    yield
    success = true
  ensure
    duration = monotonic_ms - started
    # Metrics are best-effort: an emit failure mid-finalize must not
    # corrupt the job result the caller already produced.
    begin
      finalize(success, duration, tags: tags, sample_rate: rate)
    rescue StandardError => e
      self.class.send(:handle_error, e)
    end
  end
end