Skip to content

Commit c48990d

Browse files
committed
feat: metrics integration for sidekiq
1 parent f060a57 commit c48990d

File tree

12 files changed

+463
-48
lines changed

12 files changed

+463
-48
lines changed

instrumentation/base/lib/opentelemetry/instrumentation/base.rb

+10
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,16 @@ def metrics_env_var_name
472472
end
473473
end
474474

475+
def metrics_enabled_by_env_var?
476+
var_name = name.dup
477+
var_name.upcase!
478+
var_name.gsub!('::', '_')
479+
var_name.gsub!('OPENTELEMETRY_', 'OTEL_RUBY_')
480+
var_name << '_METRICS_ENABLED'
481+
482+
ENV.key?(var_name) && ENV[var_name] != 'false'
483+
end
484+
475485
# Checks to see if the user has passed any environment variables that set options
476486
# for instrumentation. By convention, the environment variable will be the name
477487
# of the instrumentation, uppercased, with '::' replaced by underscores,

instrumentation/concurrent_ruby/opentelemetry-instrumentation-concurrent_ruby.gemspec

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ Gem::Specification.new do |spec|
2626
spec.required_ruby_version = '>= 3.0'
2727

2828
spec.add_dependency 'opentelemetry-api', '~> 1.0'
29+
spec.add_dependency 'opentelemetry-metrics-api', '~> 1.0'
2930
spec.add_dependency 'opentelemetry-instrumentation-base', '~> 0.22.1'
3031

3132
spec.add_development_dependency 'appraisal', '~> 2.5'

instrumentation/sidekiq/Appraisals

+34-19
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,39 @@
11
# frozen_string_literal: true
22

3-
appraise 'sidekiq-7.0' do
4-
gem 'sidekiq', '~> 7.0'
5-
end
6-
7-
appraise 'sidekiq-6.5' do
8-
gem 'sidekiq', '>= 6.5', '< 7.0'
9-
end
3+
{
4+
'sidekiq-7.0' => [['sidekiq', '~> 7.0']],
5+
'sidekiq-6.5' => [['sidekiq', '>= 6.5', '< 7.0']],
6+
'sidekiq-6.0' => [
7+
['sidekiq', '>= 6.0', '< 6.5'],
8+
['redis', '< 4.8']
9+
],
10+
'sidekiq-5.2' => [
11+
['sidekiq', '~> 5.2'],
12+
['redis', '< 4.8']
13+
],
14+
'sidekiq-4.2' => [
15+
['sidekiq', '~> 4.2'],
16+
['redis', '< 4.8']
17+
]
18+
}.each do |gemfile_name, specs|
19+
appraise gemfile_name do
20+
specs.each do |spec|
21+
gem *spec
22+
remove_gem 'opentelemetry-metrics-api'
23+
remove_gem 'opentelemetry-metrics-sdk'
24+
end
25+
end
1026

11-
appraise 'sidekiq-6.0' do
12-
gem 'sidekiq', '>= 6.0', '< 6.5'
13-
gem 'redis', '< 4.8'
14-
end
15-
16-
appraise 'sidekiq-5.2' do
17-
gem 'sidekiq', '~> 5.2'
18-
gem 'redis', '< 4.8'
19-
end
27+
appraise "#{gemfile_name}-metrics-api" do
28+
specs.each do |spec|
29+
gem *spec
30+
remove_gem 'opentelemetry-metrics-sdk'
31+
end
32+
end
2033

21-
appraise 'sidekiq-4.2' do
22-
gem 'sidekiq', '~> 4.2'
23-
gem 'redis', '< 4.8'
34+
appraise "#{gemfile_name}-metrics-sdk" do
35+
specs.each do |spec|
36+
gem *spec
37+
end
38+
end
2439
end

instrumentation/sidekiq/Gemfile

+14
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,22 @@ source 'https://rubygems.org'
88

99
gemspec
1010

11+
# FIXME: the metrics-api is behind the metrics-sdk gem for some reason; bundle from git for now
12+
OTEL_RUBY_GEM = lambda do |short_name|
13+
short_name = short_name.split(/-|_/)
14+
long_name = ['opentelemetry', *short_name].join('-')
15+
16+
gem long_name,
17+
git: 'https://www.github.com/open-telemetry/opentelemetry-ruby',
18+
glob: "#{short_name.join('_')}/*.gemspec",
19+
ref: '035c32ad9791f6200733e087f2ee49e0a615879a'
20+
end
21+
22+
OTEL_RUBY_GEM['metrics-api']
23+
1124
group :test do
1225
gem 'opentelemetry-instrumentation-base', path: '../base'
1326
gem 'opentelemetry-instrumentation-redis', path: '../redis'
27+
OTEL_RUBY_GEM['metrics-sdk']
1428
gem 'pry-byebug'
1529
end

instrumentation/sidekiq/lib/opentelemetry/instrumentation/sidekiq/instrumentation.rb

+49
Original file line numberDiff line numberDiff line change
@@ -107,14 +107,63 @@ class Instrumentation < OpenTelemetry::Instrumentation::Base
107107
option :trace_poller_wait, default: false, validate: :boolean
108108
option :trace_processor_process_one, default: false, validate: :boolean
109109
option :peer_service, default: nil, validate: :string
110+
option :metrics, default: false, validate: :boolean
111+
112+
# FIXME: descriptions?
113+
114+
if defined?(OpenTelemetry::Metrics)
115+
counter 'messaging.client.sent.messages'
116+
histogram 'messaging.client.operation.duration', unit: 's' # FIXME: UCUM::S
117+
counter 'messaging.client.consumed.messages'
118+
histogram 'messaging.process.duration', unit: 's'
119+
120+
# FIXME: not semconv
121+
gauge 'messaging.queue.latency', unit: 's'
122+
end
123+
124+
# FIXME: upstream
125+
def counter(name)
126+
get_instrument(:counter, name)
127+
end
128+
129+
# FIXME: upstream
130+
def histogram(name)
131+
get_instrument(:histogram, name)
132+
end
133+
134+
# FIXME: upstream
135+
def gauge(name)
136+
get_instrument(:gauge, name)
137+
end
110138

111139
private
112140

141+
def get_instrument(kind, name)
142+
return unless metrics_enabled?
143+
144+
@instruments ||= {}
145+
@instruments[[kind, name]] ||= create_configured_instrument(kind, name)
146+
end
147+
148+
def create_configured_instrument(kind, name)
149+
config = @instrument_configs[[kind, name]]
150+
151+
if config.nil?
152+
Kernel.warn("unconfigured instrument requested: #{kind} of '#{name}'")
153+
return
154+
end
155+
156+
# FIXME: some of these have different opts;
157+
# should verify that they work before this point.
158+
meter.public_send(:"create_#{kind}", name, **config)
159+
end
160+
113161
def gem_version
114162
Gem::Version.new(::Sidekiq::VERSION)
115163
end
116164

117165
def require_dependencies
166+
require_relative 'middlewares/common'
118167
require_relative 'middlewares/client/tracer_middleware'
119168
require_relative 'middlewares/server/tracer_middleware'
120169

instrumentation/sidekiq/lib/opentelemetry/instrumentation/sidekiq/middlewares/client/tracer_middleware.rb

+39-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
#
55
# SPDX-License-Identifier: Apache-2.0
66

7+
require_relative '../common'
8+
79
module OpenTelemetry
810
module Instrumentation
911
module Sidekiq
@@ -12,6 +14,7 @@ module Client
1214
# TracerMiddleware propagates context and instruments Sidekiq client
1315
# by way of its middleware system
1416
class TracerMiddleware
17+
include Common
1518
include ::Sidekiq::ClientMiddleware if defined?(::Sidekiq::ClientMiddleware)
1619

1720
def call(_worker_class, job, _queue, _redis_pool)
@@ -33,17 +36,50 @@ def call(_worker_class, job, _queue, _redis_pool)
3336
OpenTelemetry.propagation.inject(job)
3437
span.add_event('created_at', timestamp: job['created_at'])
3538
yield
39+
end.tap do
40+
# FIXME: is it possible/necessary to detect failures here? Does sidekiq bubble them up the middlewares?
41+
count_sent_message(job)
3642
end
3743
end
3844

3945
private
4046

41-
def instrumentation_config
42-
Sidekiq::Instrumentation.instance.config
47+
def count_sent_message(job)
48+
with_meter do |_meter|
49+
counter_attributes = metrics_attributes(job).merge(
50+
{
51+
'messaging.operation.name' => 'create'
52+
# server.address => # FIXME: required if available
53+
# messaging.destination.partition.id => FIXME: recommended
54+
# server.port => # FIXME: recommended
55+
}
56+
)
57+
58+
counter = messaging_client_sent_messages_counter
59+
counter.add(1, attributes: counter_attributes)
60+
end
61+
end
62+
63+
def messaging_client_sent_messages_counter
64+
instrumentation.counter('messaging.client.sent.messages')
4365
end
4466

4567
def tracer
46-
Sidekiq::Instrumentation.instance.tracer
68+
instrumentation.tracer
69+
end
70+
71+
def with_meter(&block)
72+
instrumentation.with_meter(&block)
73+
end
74+
75+
def metrics_attributes(job)
76+
{
77+
'messaging.system' => 'sidekiq', # FIXME: metrics semconv
78+
'messaging.destination.name' => job['queue'] # FIXME: metrics semconv
79+
# server.address => # FIXME: required if available
80+
# messaging.destination.partition.id => FIXME: recommended
81+
# server.port => # FIXME: recommended
82+
}
4783
end
4884
end
4985
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# frozen_string_literal: true
2+
3+
# Copyright The OpenTelemetry Authors
4+
#
5+
# SPDX-License-Identifier: Apache-2.0
6+
7+
module OpenTelemetry
8+
module Instrumentation
9+
module Sidekiq
10+
module Middlewares
11+
module Common
12+
private
13+
14+
def instrumentation
15+
Sidekiq::Instrumentation.instance
16+
end
17+
18+
def instrumentation_config
19+
Sidekiq::Instrumentation.instance.config
20+
end
21+
22+
# Bypasses _all_ enclosed logic unless metrics are enabled
23+
def with_meter(&block)
24+
instrumentation.with_meter(&block)
25+
end
26+
27+
# time an inner block and yield the duration to the given callback
28+
def timed(callback)
29+
return yield unless metrics_enabled?
30+
31+
t0 = monotonic_now
32+
33+
yield.tap do
34+
callback.call(monotonic_now - t0)
35+
end
36+
end
37+
38+
# FIXME: is this a util somewhere
39+
def realtime_now
40+
Process.clock_gettime(Process::CLOCK_REALTIME)
41+
end
42+
43+
def monotonic_now
44+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
45+
end
46+
47+
def tracer
48+
instrumentation.tracer
49+
end
50+
51+
def metrics_enabled?
52+
instrumentation.metrics_enabled?
53+
end
54+
end
55+
end
56+
end
57+
end
58+
end

0 commit comments

Comments
 (0)