diff --git a/metrics_api/lib/opentelemetry/internal/proxy_instrument.rb b/metrics_api/lib/opentelemetry/internal/proxy_instrument.rb index 117a400778..365446ad59 100644 --- a/metrics_api/lib/opentelemetry/internal/proxy_instrument.rb +++ b/metrics_api/lib/opentelemetry/internal/proxy_instrument.rb @@ -5,34 +5,18 @@ # SPDX-License-Identifier: Apache-2.0 module OpenTelemetry - module Internal - # @api private - class ProxyInstrument - def initialize(kind, name, unit, desc, callable) - @kind = kind - @name = name - @unit = unit - @desc = desc - @callable = callable - @delegate = nil - end - - def upgrade_with(meter) - @delegate = case @kind - when :counter, :histogram, :up_down_counter - meter.send("create_#{@kind}", @name, unit: @unit, description: @desc) - when :observable_counter, :observable_gauge, :observable_up_down_counter - meter.send("create_#{@kind}", @name, unit: @unit, description: @desc, callback: @callback) - end - end - - def add(amount, attributes: nil) - @delegate&.add(amount, attributes: attributes) - end - - def record(amount, attributes: nil) - @delegate&.record(amount, attributes: attributes) - end + module Metrics + module ProxyInstrument end end end + +require 'opentelemetry/internal/proxy_instrument/delegate_synchronous_instrument' +require 'opentelemetry/internal/proxy_instrument/counter' +require 'opentelemetry/internal/proxy_instrument/histogram' +require 'opentelemetry/internal/proxy_instrument/up_down_counter' + +require 'opentelemetry/internal/proxy_instrument/delegate_asynchronous_instrument' +require 'opentelemetry/internal/proxy_instrument/observable_counter' +require 'opentelemetry/internal/proxy_instrument/observable_gauge' +require 'opentelemetry/internal/proxy_instrument/observable_up_down_counter' diff --git a/metrics_api/lib/opentelemetry/internal/proxy_instrument/counter.rb b/metrics_api/lib/opentelemetry/internal/proxy_instrument/counter.rb new file mode 100644 index 0000000000..a44cfd5795 --- /dev/null +++ b/metrics_api/lib/opentelemetry/internal/proxy_instrument/counter.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +module OpenTelemetry + module Internal + module ProxyInstrument + # @api private + class Counter < Metrics::Instrument::Counter + include DelegateSynchronousInstrument + + def add(increment, attributes: nil) + @delegate_mutex.synchronize do + if @delegate.nil? + super + else + @delegate.add(increment, attributes: attributes) + end + end + end + end + end + end +end diff --git a/metrics_api/lib/opentelemetry/internal/proxy_instrument/delegate_asynchronous_instrument.rb b/metrics_api/lib/opentelemetry/internal/proxy_instrument/delegate_asynchronous_instrument.rb new file mode 100644 index 0000000000..021682e08b --- /dev/null +++ b/metrics_api/lib/opentelemetry/internal/proxy_instrument/delegate_asynchronous_instrument.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +module OpenTelemetry + module Internal + module ProxyInstrument + # @api private + module DelegateAsynchronousInstrument + def initialize(*args, **kwargs) + super + + @delegate_mutex = Mutex.new + @delegate = nil + end + + def delegate=(instrument) + @delegate_mutex.synchronize do + if @delegate.nil? + @delegate = instrument + else + OpenTelemetry.logger.warn( + 'Attempt to reset delegate in Asynchronous ProxyInstrument ignored' + ) + end + end + end + + def register_callbacks(*callbacks) + @delegate_mutex.synchronize do + if @delegate.nil? + super + else + @delegate.register_callbacks(*callbacks) + end + end + end + + def unregister_callbacks(*callbacks) + @delegate_mutex.synchronize do + if @delegate.nil? + super + else + @delegate.unregister_callbacks(*callbacks) + end + end + end + end + end + end +end diff --git a/metrics_api/lib/opentelemetry/internal/proxy_instrument/delegate_synchronous_instrument.rb b/metrics_api/lib/opentelemetry/internal/proxy_instrument/delegate_synchronous_instrument.rb new file mode 100644 index 0000000000..6f89a2fd6c --- /dev/null +++ b/metrics_api/lib/opentelemetry/internal/proxy_instrument/delegate_synchronous_instrument.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +module OpenTelemetry + module Internal + module ProxyInstrument + # @api private + module DelegateSynchronousInstrument + def initialize(*args, **kwargs) + super + + @delegate_mutex = Mutex.new + @delegate = nil + end + + def delegate=(instrument) + @delegate_mutex.synchronize do + if @delegate.nil? + @delegate = instrument + else + OpenTelemetry.logger.warn(' + Attempt to reset delegate in Synchronous ProxyInstrument ignored' + ) + end + end + end + end + end + end +end diff --git a/metrics_api/lib/opentelemetry/internal/proxy_instrument/histogram.rb b/metrics_api/lib/opentelemetry/internal/proxy_instrument/histogram.rb new file mode 100644 index 0000000000..fd4e09b748 --- /dev/null +++ b/metrics_api/lib/opentelemetry/internal/proxy_instrument/histogram.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +module OpenTelemetry + module Internal + module ProxyInstrument + # @api private + class Histogram < Metrics::Instrument::Histogram + include DelegateSynchronousInstrument + + def record(amount, attributes: nil) + @delegate_mutex.synchronize do + if @delegate.nil? + super + else + @delegate.record(amount, attributes: attributes) + end + end + end + end + end + end +end diff --git a/metrics_api/lib/opentelemetry/internal/proxy_instrument/observable_counter.rb b/metrics_api/lib/opentelemetry/internal/proxy_instrument/observable_counter.rb new file mode 100644 index 0000000000..6747e27f97 --- /dev/null +++ b/metrics_api/lib/opentelemetry/internal/proxy_instrument/observable_counter.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +module OpenTelemetry + module Internal + module ProxyInstrument + # @api private + class ObservableCounter < Metrics::Instrument::ObservableCounter + include DelegateAsynchronousInstrument + end + end + end +end diff --git a/metrics_api/lib/opentelemetry/internal/proxy_instrument/observable_gauge.rb b/metrics_api/lib/opentelemetry/internal/proxy_instrument/observable_gauge.rb new file mode 100644 index 0000000000..9862458a67 --- /dev/null +++ b/metrics_api/lib/opentelemetry/internal/proxy_instrument/observable_gauge.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +module OpenTelemetry + module Internal + module ProxyInstrument + # @api private + class ObservableGauge < Metrics::Instrument::ObservableGauge + include DelegateAsynchronousInstrument + end + end + end +end diff --git a/metrics_api/lib/opentelemetry/internal/proxy_instrument/observable_up_down_counter.rb b/metrics_api/lib/opentelemetry/internal/proxy_instrument/observable_up_down_counter.rb new file mode 100644 index 0000000000..d31d3f2f9b --- /dev/null +++ b/metrics_api/lib/opentelemetry/internal/proxy_instrument/observable_up_down_counter.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +module OpenTelemetry + module Internal + module ProxyInstrument + # @api private + class ObservableUpDownCounter < Metrics::Instrument::ObservableUpDownCounter + include DelegateAsynchronousInstrument + end + end + end +end diff --git a/metrics_api/lib/opentelemetry/internal/proxy_instrument/up_down_counter.rb b/metrics_api/lib/opentelemetry/internal/proxy_instrument/up_down_counter.rb new file mode 100644 index 0000000000..0df4f18265 --- /dev/null +++ b/metrics_api/lib/opentelemetry/internal/proxy_instrument/up_down_counter.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +module OpenTelemetry + module Internal + module ProxyInstrument + # @api private + class UpDownCounter < Metrics::Instrument::UpDownCounter + include DelegateSynchronousInstrument + + def add(amount, attributes: nil) + @delegate_mutex.synchronize do + if @delegate.nil? + super + else + @delegate.add(amount, attributes: attributes) + end + end + end + end + end + end +end diff --git a/metrics_api/lib/opentelemetry/internal/proxy_meter.rb b/metrics_api/lib/opentelemetry/internal/proxy_meter.rb index 74d497c684..04f8da2e8b 100644 --- a/metrics_api/lib/opentelemetry/internal/proxy_meter.rb +++ b/metrics_api/lib/opentelemetry/internal/proxy_meter.rb @@ -8,16 +8,15 @@ module OpenTelemetry module Internal # @api private # - # {ProxyMeter} is an implementation of {OpenTelemetry::Trace::Meter}. It is returned from - # the ProxyMeterProvider until a delegate meter provider is installed. After the delegate - # meter provider is installed, the ProxyMeter will delegate to the corresponding "real" - # meter. + # {ProxyMeter} is an implementation of {OpenTelemetry::Metrics::Meter}. + # It is returned from the ProxyMeterProvider until a delegate meter provider + # is installed. After the delegate meter provider is installed, + # the ProxyMeter will delegate to the corresponding "real" meter. class ProxyMeter < Metrics::Meter - # Returns a new {ProxyMeter} instance. - # - # @return [ProxyMeter] - def initialize + def initialize(*args, **kwargs) super + + @delegate_mutex = Mutex.new @delegate = nil end @@ -26,31 +25,193 @@ def initialize # # @param [Meter] meter The Meter to delegate to def delegate=(meter) - @mutex.synchronize do + @delegate_mutex.synchronize do if @delegate.nil? @delegate = meter - @instrument_registry.each_value { |instrument| instrument.upgrade_with(meter) } + + @instrument_registry.each_value do |proxy_instrument| + proxy_instrument.delegate = + case proxy_instrument + when ProxyInstrument::Counter + meter.create_counter( + proxy_instrument.name, + unit: proxy_instrument.unit, + description: proxy_instrument.description, + advice: proxy_instrument.advice + ) + when ProxyInstrument::Histogram + meter.create_histogram( + proxy_instrument.name, + unit: proxy_instrument.unit, + description: proxy_instrument.description, + advice: proxy_instrument.advice + ) + when ProxyInstrument::UpDownCounter + meter.create_up_down_counter( + proxy_instrument.name, + unit: proxy_instrument.unit, + description: proxy_instrument.description, + advice: proxy_instrument.advice + ) + when ProxyInstrument::ObservableCounter + meter.create_observable_counter( + proxy_instrument.name, + unit: proxy_instrument.unit, + description: proxy_instrument.description, + callbacks: proxy_instrument.callbacks + ) + when ProxyInstrument::ObservableGauge + meter.create_observable_gauge( + proxy_instrument.name, + unit: proxy_instrument.unit, + description: proxy_instrument.description, + callbacks: proxy_instrument.callbacks + ) + when ProxyInstrument::ObservableUpDownCounter + meter.create_observable_up_down_counter( + proxy_instrument.name, + unit: proxy_instrument.unit, + description: proxy_instrument.description, + callbacks: proxy_instrument.callbacks + ) + end + end else OpenTelemetry.logger.warn 'Attempt to reset delegate in ProxyMeter ignored.' end end end - private + def create_counter(name, unit: nil, description: nil, advice: nil) + instrument = @delegate_mutex.synchronize do + if @delegate.nil? + ProxyInstrument::Counter.new( + name, + unit: unit, + description: description, + advice: advice + ) + else + @delegate.create_counter( + name, + unit: unit, + description: description, + advice: advice + ) + end + end + + register_instrument(name, instrument) + end + + def create_histogram(name, unit: nil, description: nil, advice: nil) + instrument = @delegate_mutex.synchronize do + if @delegate.nil? + ProxyInstrument::Histogram.new( + name, + unit: unit, + description: description, + advice: advice + ) + else + @delegate.create_histogram( + name, + unit: unit, + description: description, + advice: advice + ) + end + end - def create_instrument(kind, name, unit, description, callback) - super do - next ProxyInstrument.new(kind, name, unit, description, callback) if @delegate.nil? + register_instrument(name, instrument) + end - case kind - when :counter then @delegate.create_counter(name, unit: unit, description: description) - when :histogram then @delegate.create_histogram(name, unit: unit, description: description) - when :up_down_counter then @delegate.create_up_down_counter(name, unit: unit, description: description) - when :observable_counter then @delegate.create_observable_counter(name, unit: unit, description: description, callback: callback) - when :observable_gauge then @delegate.create_observable_gauge(name, unit: unit, description: description, callback: callback) - when :observable_up_down_counter then @delegate.create_observable_up_down_counter(name, unit: unit, description: description, callback: callback) + def create_up_down_counter(name, unit: nil, description: nil, advice: nil) + instrument = @delegate_mutex.synchronize do + if @delegate.nil? + ProxyInstrument::UpDownCounter.new( + name, + unit: unit, + description: description, + advice: advice + ) + else + @delegate.create_up_down_counter( + name, + unit: unit, + description: description, + advice: advice + ) end end + + register_instrument(name, instrument) + end + + def create_observable_counter(name, unit: nil, description: nil, callbacks: nil) + instrument = @delegate_mutex.synchronize do + if @delegate.nil? + ProxyInstrument::ObservableCounter.new( + name, + unit: unit, + description: description, + callbacks: callbacks + ) + else + @delegate.create_observable_counter( + name, + unit: unit, + description: description, + callbacks: callbacks + ) + end + end + + register_instrument(name, instrument) + end + + def create_observable_gauge(name, unit: nil, description: nil, callbacks: nil) + instrument = @delegate_mutex.synchronize do + if @delegate.nil? + ProxyInstrument::ObservableGauge.new( + name, + unit: unit, + description: description, + callbacks: callbacks + ) + else + @delegate.create_observable_gauge( + name, + unit: unit, + description: description, + callbacks: callbacks + ) + end + end + + register_instrument(name, instrument) + end + + def create_observable_up_down_counter(name, unit: nil, description: nil, callbacks: nil) + instrument = @delegate_mutex.synchronize do + if @delegate.nil? + ProxyInstrument::ObservableUpDownCounter.new( + name, + unit: unit, + description: description, + callbacks: callbacks + ) + else + @delegate.create_observable_up_down_counter( + name, + unit: unit, + description: description, + callbacks: callbacks + ) + end + end + + register_instrument(name, instrument) end end end diff --git a/metrics_api/lib/opentelemetry/internal/proxy_meter_provider.rb b/metrics_api/lib/opentelemetry/internal/proxy_meter_provider.rb index 915c4994bc..99a122ee8b 100644 --- a/metrics_api/lib/opentelemetry/internal/proxy_meter_provider.rb +++ b/metrics_api/lib/opentelemetry/internal/proxy_meter_provider.rb @@ -13,45 +13,61 @@ module Internal # It delegates to a "real" MeterProvider after the global meter provider is registered. # It returns {ProxyMeter} instances until the delegate is installed. class ProxyMeterProvider < Metrics::MeterProvider - Key = Struct.new(:name, :version) - private_constant(:Key) + def initialize(*args, **kwargs) + super - # Returns a new {ProxyMeterProvider} instance. - # - # @return [ProxyMeterProvider] - def initialize - @mutex = Mutex.new - @registry = {} + @delegate_mutex = Mutex.new @delegate = nil end # Set the delegate Meter provider. If this is called more than once, a warning will # be logged and superfluous calls will be ignored. # - # @param [MeterProvider] provider The Meter provider to delegate to - def delegate=(provider) - unless @delegate.nil? - OpenTelemetry.logger.warn 'Attempt to reset delegate in ProxyMeterProvider ignored.' - return - end + # @param [MeterProvider] meter_provider The Meter provider to delegate to + def delegate=(meter_provider) + @delegate_mutex.synchronize do + if @delegate.nil? + @delegate = meter_provider - @mutex.synchronize do - @delegate = provider - @registry.each { |key, meter| meter.delegate = provider.meter(key.name, version: key.version) } + @meter_registry.each do |key, proxy_meter| + proxy_meter.delegate = meter_provider.meter( + proxy_meter.name, + version: proxy_meter.version, + schema_url: proxy_meter.schema_url, + attributes: proxy_meter.attributes, + ) + end + else + OpenTelemetry.logger.warn 'Attempt to reset delegate in ProxyMeterProvider ignored.' + end end end - # Returns a {Meter} instance. - # - # @param [optional String] name Instrumentation package name - # @param [optional String] version Instrumentation package version - # - # @return [Meter] - def meter(name = nil, version: nil) - @mutex.synchronize do - return @delegate.meter(name, version: version) unless @delegate.nil? + # @api private + def meter(name, version: nil, schema_url: nil, attributes: nil) + @delegate_mutex.synchronize do + if @delegate.nil? + proxy_meter = ProxyMeter.new( + name, + version: version, + schema_url: schema_url, + attributes: attributes + ) + key = Key.new( + proxy_meter.name, + proxy_meter.version, + proxy_meter.schema_url + ) - @registry[Key.new(name, version)] ||= ProxyMeter.new + @meter_registry[key] ||= proxy_meter + else + @delegate.meter( + name, + version: version, + schema_url: schema_url, + attributes: attributes + ) + end end end end diff --git a/metrics_api/lib/opentelemetry/metrics/instrument/asynchronous_instrument.rb b/metrics_api/lib/opentelemetry/metrics/instrument/asynchronous_instrument.rb index 0d05809bab..4af592b16f 100644 --- a/metrics_api/lib/opentelemetry/metrics/instrument/asynchronous_instrument.rb +++ b/metrics_api/lib/opentelemetry/metrics/instrument/asynchronous_instrument.rb @@ -12,11 +12,13 @@ class AsynchronousInstrument attr_reader :name, :unit, :description, :callbacks # @api private - def initialize(name, unit: nil, description: nil, callbacks: nil) + def initialize(name, unit: nil, description: nil, callbacks: nil, meter: nil) @name = name @unit = unit || '' @description = description || '' @callbacks = callbacks ? Array(callbacks) : [] + + @meter = meter end # @param callbacks [Proc, Array] diff --git a/metrics_api/lib/opentelemetry/metrics/instrument/counter.rb b/metrics_api/lib/opentelemetry/metrics/instrument/counter.rb index e3ac20c278..d9d5e42b2c 100644 --- a/metrics_api/lib/opentelemetry/metrics/instrument/counter.rb +++ b/metrics_api/lib/opentelemetry/metrics/instrument/counter.rb @@ -9,6 +9,10 @@ module Metrics module Instrument # No-op implementation of Counter. class Counter < SynchronousInstrument + def kind + :counter + end + # Increment the Counter by a fixed amount. # # @param [Numeric] increment The increment amount, which MUST be a non-negative numeric value. diff --git a/metrics_api/lib/opentelemetry/metrics/instrument/histogram.rb b/metrics_api/lib/opentelemetry/metrics/instrument/histogram.rb index 1b514094e0..dfab54bafa 100644 --- a/metrics_api/lib/opentelemetry/metrics/instrument/histogram.rb +++ b/metrics_api/lib/opentelemetry/metrics/instrument/histogram.rb @@ -9,6 +9,10 @@ module Metrics module Instrument # No-op implementation of Histogram. class Histogram < SynchronousInstrument + def kind + :histogram + end + # Updates the statistics with the specified amount. # # @param [Numeric] amount The amount of the Measurement, which MUST be a non-negative numeric value. diff --git a/metrics_api/lib/opentelemetry/metrics/instrument/observable_counter.rb b/metrics_api/lib/opentelemetry/metrics/instrument/observable_counter.rb index 34d63d3969..bee63005c7 100644 --- a/metrics_api/lib/opentelemetry/metrics/instrument/observable_counter.rb +++ b/metrics_api/lib/opentelemetry/metrics/instrument/observable_counter.rb @@ -9,6 +9,9 @@ module Metrics module Instrument # No-op implementation of ObservableCounter. class ObservableCounter < AsynchronousInstrument + def kind + :observable_counter + end end end end diff --git a/metrics_api/lib/opentelemetry/metrics/instrument/observable_gauge.rb b/metrics_api/lib/opentelemetry/metrics/instrument/observable_gauge.rb index a604775959..117511aff6 100644 --- a/metrics_api/lib/opentelemetry/metrics/instrument/observable_gauge.rb +++ b/metrics_api/lib/opentelemetry/metrics/instrument/observable_gauge.rb @@ -9,6 +9,9 @@ module Metrics module Instrument # No-op implementation of ObservableGauge. class ObservableGauge < AsynchronousInstrument + def kind + :observable_gauge + end end end end diff --git a/metrics_api/lib/opentelemetry/metrics/instrument/observable_up_down_counter.rb b/metrics_api/lib/opentelemetry/metrics/instrument/observable_up_down_counter.rb index 8f685906c0..9c44230295 100644 --- a/metrics_api/lib/opentelemetry/metrics/instrument/observable_up_down_counter.rb +++ b/metrics_api/lib/opentelemetry/metrics/instrument/observable_up_down_counter.rb @@ -9,6 +9,9 @@ module Metrics module Instrument # No-op implementation of ObservableUpDownCounter. class ObservableUpDownCounter < AsynchronousInstrument + def kind + :observable_up_down_counter + end end end end diff --git a/metrics_api/lib/opentelemetry/metrics/instrument/synchronous_instrument.rb b/metrics_api/lib/opentelemetry/metrics/instrument/synchronous_instrument.rb index 7452fec049..a3a34a5248 100644 --- a/metrics_api/lib/opentelemetry/metrics/instrument/synchronous_instrument.rb +++ b/metrics_api/lib/opentelemetry/metrics/instrument/synchronous_instrument.rb @@ -12,11 +12,34 @@ class SynchronousInstrument attr_reader :name, :unit, :description, :advice # @api private - def initialize(name, unit: nil, description: nil, advice: nil) + def initialize(name, unit: nil, description: nil, advice: nil, meter: nil) @name = name @unit = unit || '' @description = description || '' @advice = advice || {} + + @meter = meter + + @mutex = Mutex.new + @metric_streams = [] + end + + # @api private + def add_metric_stream(metric_stream) + @mutex.synchronize do + @metric_streams.push(metric_stream) + nil + end + end + + private + + def update(value, attributes) + @mutex.synchronize do + @metric_streams.each do |metric_stream| + metric_stream.update(value, attributes) + end + end end end end diff --git a/metrics_api/lib/opentelemetry/metrics/instrument/up_down_counter.rb b/metrics_api/lib/opentelemetry/metrics/instrument/up_down_counter.rb index dc90493504..e4984a3240 100644 --- a/metrics_api/lib/opentelemetry/metrics/instrument/up_down_counter.rb +++ b/metrics_api/lib/opentelemetry/metrics/instrument/up_down_counter.rb @@ -9,6 +9,10 @@ module Metrics module Instrument # No-op implementation of UpDownCounter. class UpDownCounter < SynchronousInstrument + def kind + :up_down_counter + end + # Increment or decrement the UpDownCounter by a fixed amount. # # @param [Numeric] amount The amount to be added, can be positive, negative or zero. diff --git a/metrics_api/lib/opentelemetry/metrics/meter.rb b/metrics_api/lib/opentelemetry/metrics/meter.rb index 37616415fe..16304fbcc2 100644 --- a/metrics_api/lib/opentelemetry/metrics/meter.rb +++ b/metrics_api/lib/opentelemetry/metrics/meter.rb @@ -24,17 +24,27 @@ class Meter :NOOP_OBSERVABLE_UP_DOWN_COUNTER ) - attr_reader :name, :version, :schema_url, :attributes + attr_reader( + :name, + :version, + :schema_url, + :attributes, + :instrumentation_scope + ) # @api private - def initialize(name, version: nil, schema_url: nil, attributes: nil) + def initialize(name, version: nil, schema_url: nil, attributes: nil, meter_provider: nil) @name = name @version = version || '' @schema_url = schema_url || '' @attributes = attributes || {} + @meter_provider = meter_provider + @mutex = Mutex.new @instrument_registry = {} + + @instrumentation_scope = nil end # @param name [String] @@ -51,7 +61,7 @@ def initialize(name, version: nil, schema_url: nil, attributes: nil) # # @return [Instrument::Counter] def create_counter(name, unit: nil, description: nil, advice: nil) - create_instrument(:counter, name, unit, description, advice, nil) { NOOP_COUNTER } + register_instrument(name, NOOP_COUNTER) end # @param name [String] @@ -68,7 +78,7 @@ def create_counter(name, unit: nil, description: nil, advice: nil) # # @return [Instrument::Histogram] def create_histogram(name, unit: nil, description: nil, advice: nil) - create_instrument(:histogram, name, unit, description, advice, nil) { NOOP_HISTOGRAM } + register_instrument(name, NOOP_HISTOGRAM) end # @param name [String] @@ -85,7 +95,7 @@ def create_histogram(name, unit: nil, description: nil, advice: nil) # # @return [Instrument::UpDownCounter] def create_up_down_counter(name, unit: nil, description: nil, advice: nil) - create_instrument(:up_down_counter, name, unit, description, advice, nil) { NOOP_UP_DOWN_COUNTER } + register_instrument(name, NOOP_UP_DOWN_COUNTER) end # @param name [String] @@ -106,7 +116,7 @@ def create_up_down_counter(name, unit: nil, description: nil, advice: nil) # # @return [Instrument::ObservableCounter] def create_observable_counter(name, unit: nil, description: nil, callbacks: nil) - create_instrument(:observable_counter, name, unit, description, nil, callbacks) { NOOP_OBSERVABLE_COUNTER } + register_instrument(name, NOOP_OBSERVABLE_COUNTER) end # @param name [String] @@ -127,7 +137,7 @@ def create_observable_counter(name, unit: nil, description: nil, callbacks: nil) # # @return [Instrument::ObservableGauge] def create_observable_gauge(name, unit: nil, description: nil, callbacks: nil) - create_instrument(:observable_gauge, name, unit, description, nil, callbacks) { NOOP_OBSERVABLE_GAUGE } + register_instrument(name, NOOP_OBSERVABLE_GAUGE) end # @param name [String] @@ -148,12 +158,21 @@ def create_observable_gauge(name, unit: nil, description: nil, callbacks: nil) # # @return [Instrument::ObservableUpDownCounter] def create_observable_up_down_counter(name, unit: nil, description: nil, callbacks: nil) - create_instrument(:observable_up_down_counter, name, unit, description, nil, callbacks) { NOOP_OBSERVABLE_UP_DOWN_COUNTER } + register_instrument(name, NOOP_OBSERVABLE_UP_DOWN_COUNTER) + end + + # @api private + def each_instrument + @mutex.synchronize do + @instrument_registry.each do |name, instrument| + yield(name, instrument) + end + end end private - def create_instrument(kind, name, unit, description, advice, callbacks) + def register_instrument(name, instrument) name = name.downcase @mutex.synchronize do @@ -161,7 +180,7 @@ def create_instrument(kind, name, unit, description, advice, callbacks) OpenTelemetry.logger.warn("duplicate instrument registration occurred for #{name}") end - @instrument_registry[name] = yield + @instrument_registry[name] = instrument end end end diff --git a/metrics_api/lib/opentelemetry/metrics/meter_provider.rb b/metrics_api/lib/opentelemetry/metrics/meter_provider.rb index 3cd53b110c..d78542ed87 100644 --- a/metrics_api/lib/opentelemetry/metrics/meter_provider.rb +++ b/metrics_api/lib/opentelemetry/metrics/meter_provider.rb @@ -9,16 +9,19 @@ module Metrics # No-op implementation of a meter provider. class MeterProvider NOOP_METER = Meter.new('no-op') + Key = Struct.new(:name, :version, :schema_url) - private_constant :NOOP_METER + private_constant :NOOP_METER, :Key + + attr_reader :resource def initialize(resource: nil) @resource = resource @mutex = Mutex.new - @meter_registry = {} @stopped = false @metric_readers = [] + @meter_registry = {} end # @param [String] name @@ -35,6 +38,15 @@ def initialize(resource: nil) def meter(name, version: nil, schema_url: nil, attributes: nil) NOOP_METER end + + # @api private + def each_metric_reader + @mutex.synchronize do + @metric_readers.each do |metric_reader| + yield(metric_reader) + end + end + end end end end diff --git a/metrics_sdk/lib/opentelemetry/sdk/metrics/aggregation/histogram_data_point.rb b/metrics_sdk/lib/opentelemetry/sdk/metrics/aggregation/histogram_data_point.rb index 212c5ccd0e..35ecdf245b 100644 --- a/metrics_sdk/lib/opentelemetry/sdk/metrics/aggregation/histogram_data_point.rb +++ b/metrics_sdk/lib/opentelemetry/sdk/metrics/aggregation/histogram_data_point.rb @@ -10,16 +10,18 @@ module Metrics module Aggregation # TODO: Deal with this later # rubocop:disable Lint/StructNewOverride - HistogramDataPoint = Struct.new(:attributes, # optional Hash{String => String, Numeric, Boolean, Array} - :start_time_unix_nano, # Integer nanoseconds since Epoch - :time_unix_nano, # Integer nanoseconds since Epoch - :count, # Integer count is the number of values in the population. Must be non-negative. - :sum, # Integer sum of the values in the population. If count is zero then this field then this field must be zero - :bucket_counts, # optional Array[Integer] field contains the count values of histogram for each bucket. - :explicit_bounds, # Array[Float] specifies buckets with explicitly defined bounds for values. - :exemplars, # optional List of exemplars collected from measurements that were used to form the data point - :min, # optional Float min is the minimum value over (start_time, end_time]. - :max) # optional Float max is the maximum value over (start_time, end_time]. + HistogramDataPoint = Struct.new( + :attributes, # optional Hash{String => String, Numeric, Boolean, Array} + :start_time_unix_nano, # Integer nanoseconds since Epoch + :time_unix_nano, # Integer nanoseconds since Epoch + :count, # Integer count is the number of values in the population. Must be non-negative. + :sum, # Integer sum of the values in the population. If count is zero then this field then this field must be zero + :bucket_counts, # optional Array[Integer] field contains the count values of histogram for each bucket. + :explicit_bounds, # Array[Float] specifies buckets with explicitly defined bounds for values. + :exemplars, # optional List of exemplars collected from measurements that were used to form the data point + :min, # optional Float min is the minimum value over (start_time, end_time]. + :max # optional Float max is the maximum value over (start_time, end_time]. + ) # rubocop:enable Lint/StructNewOverride end end diff --git a/metrics_sdk/lib/opentelemetry/sdk/metrics/aggregation/last_value.rb b/metrics_sdk/lib/opentelemetry/sdk/metrics/aggregation/last_value.rb new file mode 100644 index 0000000000..39c194db75 --- /dev/null +++ b/metrics_sdk/lib/opentelemetry/sdk/metrics/aggregation/last_value.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +module OpenTelemetry + module SDK + module Metrics + module Aggregation + class LastValue + # TODO: implement aggregation code + def initialize + end + end + end + end + end +end diff --git a/metrics_sdk/lib/opentelemetry/sdk/metrics/aggregation/number_data_point.rb b/metrics_sdk/lib/opentelemetry/sdk/metrics/aggregation/number_data_point.rb index ae6e37eac1..518d405475 100644 --- a/metrics_sdk/lib/opentelemetry/sdk/metrics/aggregation/number_data_point.rb +++ b/metrics_sdk/lib/opentelemetry/sdk/metrics/aggregation/number_data_point.rb @@ -8,11 +8,13 @@ module OpenTelemetry module SDK module Metrics module Aggregation - NumberDataPoint = Struct.new(:attributes, # Hash{String => String, Numeric, Boolean, Array} - :start_time_unix_nano, # Integer nanoseconds since Epoch - :time_unix_nano, # Integer nanoseconds since Epoch - :value, # Integer - :exemplars) # optional List of exemplars collected from measurements that were used to form the data point + NumberDataPoint = Struct.new( + :attributes, # Hash{String => String, Numeric, Boolean, Array} + :start_time_unix_nano, # Integer nanoseconds since Epoch + :time_unix_nano, # Integer nanoseconds since Epoch + :value, # Integer + :exemplars # optional List of exemplars collected from measurements that were used to form the data point + ) end end end diff --git a/metrics_sdk/lib/opentelemetry/sdk/metrics/aggregation/sum.rb b/metrics_sdk/lib/opentelemetry/sdk/metrics/aggregation/sum.rb index a4bb50f71c..424fef3483 100644 --- a/metrics_sdk/lib/opentelemetry/sdk/metrics/aggregation/sum.rb +++ b/metrics_sdk/lib/opentelemetry/sdk/metrics/aggregation/sum.rb @@ -11,41 +11,50 @@ module Aggregation # Contains the implementation of the Sum aggregation # https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#sum-aggregation class Sum - def initialize(aggregation_temporality: :delta) + attr_reader :aggregation_temporality, :monotonic + + def initialize(aggregation_temporality: :delta, monotonic: true) @aggregation_temporality = aggregation_temporality - @data_points = {} + @monotonic = monotonic + + @number_data_points = {} + end + + def update(increment, attributes) + @number_data_points[attributes] ||= build_number_data_point(attributes) + @number_data_points[attributes].value += increment + nil end - def collect(start_time, end_time) - if @aggregation_temporality == :delta - # Set timestamps and 'move' data point values to result. - ndps = @data_points.each_value do |ndp| - ndp.start_time_unix_nano = start_time - ndp.time_unix_nano = end_time + def collect(start_time_unix_nano, end_time_unix_nano) + if aggregation_temporality == :delta + ndps = @number_data_points.map do |_attributes, ndp| + ndp.start_time_unix_nano = start_time_unix_nano + ndp.time_unix_nano = end_time_unix_nano + ndp end - @data_points.clear + @number_data_points.clear ndps else - # Update timestamps and take a snapshot. - @data_points.values.map! do |ndp| - ndp.start_time_unix_nano ||= start_time # Start time of a data point is from the first observation. - ndp.time_unix_nano = end_time + @number_data_points.map do |_attributes, ndp| + # Start time of data point is from the first observation. + ndp.start_time_unix_nano ||= start_time_unix_nano + ndp.time_unix_nano = end_time_unix_nano ndp.dup end end end - def update(increment, attributes) - ndp = @data_points[attributes] || @data_points[attributes] = NumberDataPoint.new( + private + + def build_number_data_point(attributes) + NumberDataPoint.new( attributes, nil, nil, 0, nil ) - - ndp.value += increment - nil end end end diff --git a/metrics_sdk/lib/opentelemetry/sdk/metrics/instrument.rb b/metrics_sdk/lib/opentelemetry/sdk/metrics/instrument.rb index c440634c8c..5d91f95bad 100644 --- a/metrics_sdk/lib/opentelemetry/sdk/metrics/instrument.rb +++ b/metrics_sdk/lib/opentelemetry/sdk/metrics/instrument.rb @@ -13,10 +13,10 @@ module Instrument end end -require 'opentelemetry/sdk/metrics/instrument/synchronous_instrument' require 'opentelemetry/sdk/metrics/instrument/counter' require 'opentelemetry/sdk/metrics/instrument/histogram' +require 'opentelemetry/sdk/metrics/instrument/up_down_counter' + require 'opentelemetry/sdk/metrics/instrument/observable_counter' require 'opentelemetry/sdk/metrics/instrument/observable_gauge' require 'opentelemetry/sdk/metrics/instrument/observable_up_down_counter' -require 'opentelemetry/sdk/metrics/instrument/up_down_counter' diff --git a/metrics_sdk/lib/opentelemetry/sdk/metrics/instrument/counter.rb b/metrics_sdk/lib/opentelemetry/sdk/metrics/instrument/counter.rb index 6af04b1937..b19f193d4b 100644 --- a/metrics_sdk/lib/opentelemetry/sdk/metrics/instrument/counter.rb +++ b/metrics_sdk/lib/opentelemetry/sdk/metrics/instrument/counter.rb @@ -9,14 +9,7 @@ module SDK module Metrics module Instrument # {Counter} is the SDK implementation of {OpenTelemetry::Metrics::Counter}. - class Counter < OpenTelemetry::SDK::Metrics::Instrument::SynchronousInstrument - # Returns the instrument kind as a Symbol - # - # @return [Symbol] - def instrument_kind - :counter - end - + class Counter < OpenTelemetry::Metrics::Instrument::Counter # Increment the Counter by a fixed amount. # # @param [numeric] increment The increment amount, which MUST be a non-negative numeric value. @@ -38,12 +31,6 @@ def add(increment, attributes: {}) OpenTelemetry.handle_error(exception: e) nil end - - private - - def default_aggregation - OpenTelemetry::SDK::Metrics::Aggregation::Sum.new - end end end end diff --git a/metrics_sdk/lib/opentelemetry/sdk/metrics/instrument/histogram.rb b/metrics_sdk/lib/opentelemetry/sdk/metrics/instrument/histogram.rb index 9cdcf60d80..dd9629ee1e 100644 --- a/metrics_sdk/lib/opentelemetry/sdk/metrics/instrument/histogram.rb +++ b/metrics_sdk/lib/opentelemetry/sdk/metrics/instrument/histogram.rb @@ -9,14 +9,7 @@ module SDK module Metrics module Instrument # {Histogram} is the SDK implementation of {OpenTelemetry::Metrics::Histogram}. - class Histogram < OpenTelemetry::SDK::Metrics::Instrument::SynchronousInstrument - # Returns the instrument kind as a Symbol - # - # @return [Symbol] - def instrument_kind - :histogram - end - + class Histogram < OpenTelemetry::Metrics::Instrument::Histogram # Updates the statistics with the specified amount. # # @param [numeric] amount The amount of the Measurement, which MUST be a non-negative numeric value. @@ -31,12 +24,6 @@ def record(amount, attributes: nil) OpenTelemetry.handle_error(exception: e) nil end - - private - - def default_aggregation - OpenTelemetry::SDK::Metrics::Aggregation::ExplicitBucketHistogram.new - end end end end diff --git a/metrics_sdk/lib/opentelemetry/sdk/metrics/instrument/observable_counter.rb b/metrics_sdk/lib/opentelemetry/sdk/metrics/instrument/observable_counter.rb index 4e5d49102c..bba6f478fb 100644 --- a/metrics_sdk/lib/opentelemetry/sdk/metrics/instrument/observable_counter.rb +++ b/metrics_sdk/lib/opentelemetry/sdk/metrics/instrument/observable_counter.rb @@ -10,15 +10,6 @@ module Metrics module Instrument # {ObservableCounter} is the SDK implementation of {OpenTelemetry::Metrics::ObservableCounter}. class ObservableCounter < OpenTelemetry::Metrics::Instrument::ObservableCounter - attr_reader :name, :unit, :description - - def initialize(name, unit, description, callback, meter) - @name = name - @unit = unit - @description = description - @callback = callback - @meter = meter - end end end end diff --git a/metrics_sdk/lib/opentelemetry/sdk/metrics/instrument/observable_gauge.rb b/metrics_sdk/lib/opentelemetry/sdk/metrics/instrument/observable_gauge.rb index 7a122184f4..c1240b4028 100644 --- a/metrics_sdk/lib/opentelemetry/sdk/metrics/instrument/observable_gauge.rb +++ b/metrics_sdk/lib/opentelemetry/sdk/metrics/instrument/observable_gauge.rb @@ -10,15 +10,6 @@ module Metrics module Instrument # {ObservableGauge} is the SDK implementation of {OpenTelemetry::Metrics::ObservableGauge}. class ObservableGauge < OpenTelemetry::Metrics::Instrument::ObservableGauge - attr_reader :name, :unit, :description - - def initialize(name, unit, description, callback, meter) - @name = name - @unit = unit - @description = description - @callback = callback - @meter = meter - end end end end diff --git a/metrics_sdk/lib/opentelemetry/sdk/metrics/instrument/observable_up_down_counter.rb b/metrics_sdk/lib/opentelemetry/sdk/metrics/instrument/observable_up_down_counter.rb index 8eb812c5fb..b41325efe7 100644 --- a/metrics_sdk/lib/opentelemetry/sdk/metrics/instrument/observable_up_down_counter.rb +++ b/metrics_sdk/lib/opentelemetry/sdk/metrics/instrument/observable_up_down_counter.rb @@ -10,15 +10,6 @@ module Metrics module Instrument # {ObservableUpDownCounter} is the SDK implementation of {OpenTelemetry::Metrics::ObservableUpDownCounter}. class ObservableUpDownCounter < OpenTelemetry::Metrics::Instrument::ObservableUpDownCounter - attr_reader :name, :unit, :description - - def initialize(name, unit, description, callback, meter) - @name = name - @unit = unit - @description = description - @callback = callback - @meter = meter - end end end end diff --git a/metrics_sdk/lib/opentelemetry/sdk/metrics/instrument/synchronous_instrument.rb b/metrics_sdk/lib/opentelemetry/sdk/metrics/instrument/synchronous_instrument.rb deleted file mode 100644 index f5bf321f0a..0000000000 --- a/metrics_sdk/lib/opentelemetry/sdk/metrics/instrument/synchronous_instrument.rb +++ /dev/null @@ -1,49 +0,0 @@ -# frozen_string_literal: true - -# Copyright The OpenTelemetry Authors -# -# SPDX-License-Identifier: Apache-2.0 - -module OpenTelemetry - module SDK - module Metrics - module Instrument - # {SynchronousInstrument} contains the common functionality shared across - # the synchronous instruments SDK instruments. - class SynchronousInstrument - def initialize(name, unit, description, instrumentation_scope, meter_provider) - @name = name - @unit = unit - @description = description - @instrumentation_scope = instrumentation_scope - @meter_provider = meter_provider - @metric_streams = [] - - meter_provider.register_synchronous_instrument(self) - end - - # @api private - def register_with_new_metric_store(metric_store, aggregation: default_aggregation) - ms = OpenTelemetry::SDK::Metrics::State::MetricStream.new( - @name, - @description, - @unit, - instrument_kind, - @meter_provider, - @instrumentation_scope, - aggregation - ) - @metric_streams << ms - metric_store.add_metric_stream(ms) - end - - private - - def update(value, attributes) - @metric_streams.each { |ms| ms.update(value, attributes) } - end - end - end - end - end -end diff --git a/metrics_sdk/lib/opentelemetry/sdk/metrics/instrument/up_down_counter.rb b/metrics_sdk/lib/opentelemetry/sdk/metrics/instrument/up_down_counter.rb index cf2dc0d8b4..38c9636f2e 100644 --- a/metrics_sdk/lib/opentelemetry/sdk/metrics/instrument/up_down_counter.rb +++ b/metrics_sdk/lib/opentelemetry/sdk/metrics/instrument/up_down_counter.rb @@ -9,14 +9,7 @@ module SDK module Metrics module Instrument # {UpDownCounter} is the SDK implementation of {OpenTelemetry::Metrics::UpDownCounter}. - class UpDownCounter < OpenTelemetry::SDK::Metrics::Instrument::SynchronousInstrument - # Returns the instrument kind as a Symbol - # - # @return [Symbol] - def instrument_kind - :up_down_counter - end - + class UpDownCounter < OpenTelemetry::Metrics::Instrument::UpDownCounter # Increment or decrement the UpDownCounter by a fixed amount. # # @param [Numeric] amount The amount to be added, can be positive, negative or zero. @@ -31,12 +24,6 @@ def add(amount, attributes: nil) OpenTelemetry.handle_error(exception: e) nil end - - private - - def default_aggregation - OpenTelemetry::SDK::Metrics::Aggregation::Sum.new - end end end end diff --git a/metrics_sdk/lib/opentelemetry/sdk/metrics/meter.rb b/metrics_sdk/lib/opentelemetry/sdk/metrics/meter.rb index faf8e9adb1..d5d7ebb675 100644 --- a/metrics_sdk/lib/opentelemetry/sdk/metrics/meter.rb +++ b/metrics_sdk/lib/opentelemetry/sdk/metrics/meter.rb @@ -11,38 +11,149 @@ module SDK module Metrics # {Meter} is the SDK implementation of {OpenTelemetry::Metrics::Meter}. class Meter < OpenTelemetry::Metrics::Meter - # @api private - # - # Returns a new {Meter} instance. - # - # @param [String] name Instrumentation package name - # @param [String] version Instrumentation package version - # - # @return [Meter] - def initialize(name, version, meter_provider) - @mutex = Mutex.new - @instrument_registry = {} - @instrumentation_scope = InstrumentationScope.new(name, version) - @meter_provider = meter_provider + attr_reader :instrumentation_scope + + def initialize(*args, **kwargs) + super + + @instrumentation_scope = InstrumentationScope.new( + @name, + @version, + @schema_url, + @attributes + ) + end + + def create_counter(name, unit: nil, description: nil, advice: nil) + instrument = SDK::Metrics::Instrument::Counter.new( + name, + unit: unit, + description: description, + advice: advice, + meter: self + ) + register_instrument(name, instrument) + + @meter_provider&.each_metric_reader do |metric_reader| + build_and_add_metric_stream(metric_reader.metric_store, instrument, nil) + end + + instrument + end + + def create_histogram(name, unit: nil, description: nil, advice: nil) + instrument = SDK::Metrics::Instrument::Histogram.new( + name, + unit: unit, + description: description, + advice: advice, + meter: self + ) + register_instrument(name, instrument) + + @meter_provider&.each_metric_reader do |metric_reader| + build_and_add_metric_stream(metric_reader.metric_store, instrument, nil) + end + + instrument + end + + def create_up_down_counter(name, unit: nil, description: nil, advice: nil) + instrument = SDK::Metrics::Instrument::UpDownCounter.new( + name, + unit: unit, + description: description, + advice: advice, + meter: self + ) + register_instrument(name, instrument) + + @meter_provider&.each_metric_reader do |metric_reader| + build_and_add_metric_stream(metric_reader.metric_store, instrument, nil) + end + + instrument + end + + def create_observable_counter(name, unit: nil, description: nil, callbacks: nil) + instrument = SDK::Metrics::Instrument::ObservableCounter.new( + name, + unit: unit, + description: description, + callbacks: callbacks, + meter: self + ) + register_instrument(name, instrument) + end + + def create_observable_gauge(name, unit: nil, description: nil, callbacks: nil) + instrument = SDK::Metrics::Instrument::ObservableGauge.new( + name, + unit: unit, + description: description, + callbacks: callbacks, + meter: self + ) + register_instrument(name, instrument) + end + + def create_observable_up_down_counter(name, unit: nil, description: nil, callbacks: nil) + instrument = SDK::Metrics::Instrument::ObservableUpDownCounter.new( + name, + unit: unit, + description: description, + callbacks: callbacks, + meter: self + ) + register_instrument(name, instrument) end # @api private - def add_metric_reader(metric_reader) - @instrument_registry.each do |_n, instrument| - instrument.register_with_new_metric_store(metric_reader.metric_store) + def register_metric_store(metric_store, aggregation) + each_instrument do |_name, instrument| + build_and_add_metric_stream(metric_store, instrument, aggregation) end end - def create_instrument(kind, name, unit, description, callback) - super do - case kind - when :counter then OpenTelemetry::SDK::Metrics::Instrument::Counter.new(name, unit, description, @instrumentation_scope, @meter_provider) - when :observable_counter then OpenTelemetry::SDK::Metrics::Instrument::ObservableCounter.new(name, unit, description, callback, @instrumentation_scope, @meter_provider) - when :histogram then OpenTelemetry::SDK::Metrics::Instrument::Histogram.new(name, unit, description, @instrumentation_scope, @meter_provider) - when :observable_gauge then OpenTelemetry::SDK::Metrics::Instrument::ObservableGauge.new(name, unit, description, callback, @instrumentation_scope, @meter_provider) - when :up_down_counter then OpenTelemetry::SDK::Metrics::Instrument::UpDownCounter.new(name, unit, description, @instrumentation_scope, @meter_provider) - when :observable_up_down_counter then OpenTelemetry::SDK::Metrics::Instrument::ObservableUpDownCounter.new(name, unit, description, callback, @instrumentation_scope, @meter_provider) - end + private + + def build_and_add_metric_stream(metric_store, instrument, aggregation) + metric_stream = build_metric_stream(instrument, aggregation) + + metric_store.add_metric_stream(metric_stream) + instrument.add_metric_stream(metric_stream) + end + + def build_metric_stream(instrument, aggregation) + aggregation ||= build_default_aggregation_for(instrument) + + SDK::Metrics::State::MetricStream.new( + instrument.name, + instrument.description, + instrument.unit, + instrument.kind, + @meter_provider.resource, + instrumentation_scope, + aggregation + ) + end + + def build_default_aggregation_for(instrument) + case instrument + when Instrument::Counter + Aggregation::Sum.new + when Instrument::Histogram + # TODO: check instrument's advice for explicit_bucket_boundaries + # and use it to create the default ExplicitBucketHistogram + Aggregation::ExplicitBucketHistogram.new + when Instrument::UpDownCounter + Aggregation::Sum.new + when Instrument::ObservableCounter + Aggregation::Sum.new + when Instrument::ObservableGauge + Aggregation::LastValue.new + when Instrument::ObservableUpDownCounter + Aggregation::Sum.new end end end diff --git a/metrics_sdk/lib/opentelemetry/sdk/metrics/meter_provider.rb b/metrics_sdk/lib/opentelemetry/sdk/metrics/meter_provider.rb index 205ff5db0d..bdbe2115bc 100644 --- a/metrics_sdk/lib/opentelemetry/sdk/metrics/meter_provider.rb +++ b/metrics_sdk/lib/opentelemetry/sdk/metrics/meter_provider.rb @@ -11,32 +11,38 @@ module SDK module Metrics # {MeterProvider} is the SDK implementation of {OpenTelemetry::Metrics::MeterProvider}. class MeterProvider < OpenTelemetry::Metrics::MeterProvider - Key = Struct.new(:name, :version) - private_constant(:Key) - - attr_reader :resource, :metric_readers - - def initialize(resource: OpenTelemetry::SDK::Resources::Resource.create) - @mutex = Mutex.new - @meter_registry = {} - @stopped = false - @metric_readers = [] - @resource = resource + def initialize(resource: OpenTelemetry::SDK::Resources::Resource.default) + super end - # Returns a {Meter} instance. - # - # @param [String] name Instrumentation package name - # @param [optional String] version Instrumentation package version + # @param [String] name + # Uniquely identifies the instrumentation scope, such as the instrumentation library + # (e.g. io.opentelemetry.contrib.mongodb), package, module or class name + # @param [optional String] version + # Version of the instrumentation scope if the scope has a version (e.g. a library version) + # @param [optional String] schema_url + # Schema URL that should be recorded in the emitted telemetry + # @param [optional Hash{String => String, Numeric, Boolean, Array}] attributes + # Instrumentation scope attributes to associate with emitted telemetry # - # @return [Meter] - def meter(name, version: nil) - version ||= '' - if @stopped - OpenTelemetry.logger.warn 'calling MeterProvider#meter after shutdown, a noop meter will be returned.' - OpenTelemetry::Metrics::Meter.new - else - @mutex.synchronize { @meter_registry[Key.new(name, version)] ||= Meter.new(name, version, self) } + # @return [SDK::Metrics::Meter] + def meter(name, version: nil, schema_url: nil, attributes: nil) + @mutex.synchronize do + if @stopped + OpenTelemetry.logger.warn 'calling MeterProvider#meter after shutdown, a noop meter will be returned.' + + NOOP_METER + else + key = build_key_for_meter(name, version, schema_url) + + @meter_registry[key] ||= Meter.new( + name, + version: version, + schema_url: schema_url, + attributes: attributes, + meter_provider: self + ) + end end end @@ -103,28 +109,21 @@ def force_flush(timeout: nil) # Adds a new MetricReader to this {MeterProvider}. # # @param metric_reader the new MetricReader to be added. - def add_metric_reader(metric_reader) + def add_metric_reader(metric_reader, aggregation: nil) @mutex.synchronize do if @stopped OpenTelemetry.logger.warn('calling MetricProvider#add_metric_reader after shutdown.') else @metric_readers.push(metric_reader) - @meter_registry.each_value { |meter| meter.add_metric_reader(metric_reader) } + @meter_registry.each_value do |meter| + meter.register_metric_store(metric_reader.metric_store, aggregation) + end end nil end end - # @api private - def register_synchronous_instrument(instrument) - @mutex.synchronize do - @metric_readers.each do |mr| - instrument.register_with_new_metric_store(mr.metric_store) - end - end - end - # The type of the Instrument(s) (optional). # The name of the Instrument(s). OpenTelemetry SDK authors MAY choose to support wildcard characters, with the question mark (?) matching exactly one character and the asterisk character (*) matching zero or more characters. # The name of the Meter (optional). @@ -133,6 +132,16 @@ def register_synchronous_instrument(instrument) def add_view # TODO: For each meter add this view to all applicable instruments end + + private + + def build_key_for_meter(name, version, schema_url) + if name.nil? || name.empty? + OpenTelemetry.logger.warn 'Invalid name provided to MeterProvider#meter: nil or empty' + end + + Key.new(name, version, schema_url) + end end end end diff --git a/metrics_sdk/lib/opentelemetry/sdk/metrics/state/metric_data.rb b/metrics_sdk/lib/opentelemetry/sdk/metrics/state/metric_data.rb index 9885deba71..38d1167685 100644 --- a/metrics_sdk/lib/opentelemetry/sdk/metrics/state/metric_data.rb +++ b/metrics_sdk/lib/opentelemetry/sdk/metrics/state/metric_data.rb @@ -9,15 +9,17 @@ module SDK module Metrics module State # MetricData is a Struct containing {MetricStream} data for export. - MetricData = Struct.new(:name, # String - :description, # String - :unit, # String - :instrument_kind, # Symbol - :resource, # OpenTelemetry::SDK::Resources::Resource - :instrumentation_scope, # OpenTelemetry::SDK::InstrumentationScope - :data_points, # Hash{Hash{String => String, Numeric, Boolean, Array} => Numeric} - :start_time_unix_nano, # Integer nanoseconds since Epoch - :time_unix_nano) # Integer nanoseconds since Epoch + MetricData = Struct.new( + :name, # String + :description, # String + :unit, # String + :instrument_kind, # Symbol + :resource, # OpenTelemetry::SDK::Resources::Resource + :instrumentation_scope, # OpenTelemetry::SDK::InstrumentationScope + :data_points, # Hash{Hash{String => String, Numeric, Boolean, Array} => Numeric} + :start_time_unix_nano, # Integer nanoseconds since Epoch + :time_unix_nano # Integer nanoseconds since Epoch + ) end end end diff --git a/metrics_sdk/lib/opentelemetry/sdk/metrics/state/metric_store.rb b/metrics_sdk/lib/opentelemetry/sdk/metrics/state/metric_store.rb index de2ecb83b6..9365493f2a 100644 --- a/metrics_sdk/lib/opentelemetry/sdk/metrics/state/metric_store.rb +++ b/metrics_sdk/lib/opentelemetry/sdk/metrics/state/metric_store.rb @@ -14,24 +14,28 @@ module State # public API. class MetricStore def initialize - @mutex = Mutex.new @epoch_start_time = now_in_nano @epoch_end_time = nil + + @mutex = Mutex.new @metric_streams = [] end def collect @mutex.synchronize do @epoch_end_time = now_in_nano - snapshot = @metric_streams.map { |ms| ms.collect(@epoch_start_time, @epoch_end_time) } + metric_data = @metric_streams.map do |metric_stream| + metric_stream.collect(@epoch_start_time, @epoch_end_time) + end @epoch_start_time = @epoch_end_time - snapshot + + metric_data end end def add_metric_stream(metric_stream) @mutex.synchronize do - @metric_streams = @metric_streams.dup.push(metric_stream) + @metric_streams.push(metric_stream) nil end end diff --git a/metrics_sdk/lib/opentelemetry/sdk/metrics/state/metric_stream.rb b/metrics_sdk/lib/opentelemetry/sdk/metrics/state/metric_stream.rb index d79d4a51ba..72ed09abf0 100644 --- a/metrics_sdk/lib/opentelemetry/sdk/metrics/state/metric_stream.rb +++ b/metrics_sdk/lib/opentelemetry/sdk/metrics/state/metric_stream.rb @@ -13,14 +13,22 @@ module State # The MetricStream class provides SDK internal functionality that is not a part of the # public API. class MetricStream - attr_reader :name, :description, :unit, :instrument_kind, :instrumentation_scope, :data_points + attr_reader( + :name, + :description, + :unit, + :instrument_kind, + :resource, + :instrumentation_scope, + :data_points # ?? + ) def initialize( name, description, unit, - instrument_kind, - meter_provider, + instrument_kind, # TODO: remove and replace with data_point_type?? + resource, instrumentation_scope, aggregation ) @@ -28,10 +36,11 @@ def initialize( @description = description @unit = unit @instrument_kind = instrument_kind - @meter_provider = meter_provider + @resource = resource @instrumentation_scope = instrumentation_scope @aggregation = aggregation + @data_points = [] # ?? @mutex = Mutex.new end @@ -41,8 +50,8 @@ def collect(start_time, end_time) @name, @description, @unit, - @instrument_kind, - @meter_provider.resource, + @instrument_kind, # TODO: remove and replace with data_point_type?? + @resource, @instrumentation_scope, @aggregation.collect(start_time, end_time), start_time, diff --git a/metrics_sdk/test/opentelemetry/metrics_sdk_test.rb b/metrics_sdk/test/opentelemetry/metrics_sdk_test.rb index 62a9ecf5c6..2911a6ab3b 100644 --- a/metrics_sdk/test/opentelemetry/metrics_sdk_test.rb +++ b/metrics_sdk/test/opentelemetry/metrics_sdk_test.rb @@ -18,7 +18,7 @@ # Calls before the SDK is configured return Proxy implementations _(meter_provider).must_be_instance_of OpenTelemetry::Internal::ProxyMeterProvider _(meter).must_be_instance_of OpenTelemetry::Internal::ProxyMeter - _(instrument).must_be_instance_of OpenTelemetry::Internal::ProxyInstrument + _(instrument).must_be_instance_of OpenTelemetry::Internal::ProxyInstrument::Counter OpenTelemetry::SDK.configure diff --git a/metrics_sdk/test/opentelemetry/sdk/metrics/aggregation/sum_test.rb b/metrics_sdk/test/opentelemetry/sdk/metrics/aggregation/sum_test.rb index 3c0a3931e3..fc07c55989 100644 --- a/metrics_sdk/test/opentelemetry/sdk/metrics/aggregation/sum_test.rb +++ b/metrics_sdk/test/opentelemetry/sdk/metrics/aggregation/sum_test.rb @@ -7,69 +7,179 @@ require 'test_helper' describe OpenTelemetry::SDK::Metrics::Aggregation::Sum do - let(:sum_aggregation) { OpenTelemetry::SDK::Metrics::Aggregation::Sum.new(aggregation_temporality: aggregation_temporality) } - let(:aggregation_temporality) { :delta } - - # Time in nano - let(:start_time) { (Time.now.to_r * 1_000_000_000).to_i } - let(:end_time) { ((Time.now + 60).to_r * 1_000_000_000).to_i } - - it 'sets the timestamps' do - sum_aggregation.update(0, {}) - ndp = sum_aggregation.collect(start_time, end_time)[0] - _(ndp.start_time_unix_nano).must_equal(start_time) - _(ndp.time_unix_nano).must_equal(end_time) + let(:start_time_unix_nano) { now_in_nano } + let(:end_time_unix_nano) { start_time_unix_nano + 60*(10**9) } + + describe '.new' do + it 'defaults to aggregation_temporality :delta' do + sum = build_sum + + assert(sum.aggregation_temporality == :delta) + end + + it 'defaults to monotonic true' do + sum = build_sum + + assert(sum.monotonic == true) + end end - it 'aggregates and collects' do - sum_aggregation.update(1, {}) - sum_aggregation.update(2, {}) + describe '#aggregation_temporality' do + it 'returns aggregation_temporality' do + sum = build_sum(aggregation_temporality: :delta) + assert(sum.aggregation_temporality == :delta) - sum_aggregation.update(2, 'foo' => 'bar') - sum_aggregation.update(2, 'foo' => 'bar') + sum = build_sum(aggregation_temporality: :cumulative) + assert(sum.aggregation_temporality == :cumulative) + end + end - ndps = sum_aggregation.collect(start_time, end_time) - _(ndps[0].value).must_equal(3) - _(ndps[0].attributes).must_equal({}) + describe '#monotonic' do + it 'returns monotonic' do + sum = build_sum(monotonic: true) + assert(sum.monotonic == true) - _(ndps[1].value).must_equal(4) - _(ndps[1].attributes).must_equal('foo' => 'bar') + sum = build_sum(monotonic: false) + assert(sum.monotonic == false) + end end - it 'does not aggregate between collects' do - sum_aggregation.update(1, {}) - sum_aggregation.update(2, {}) - ndps = sum_aggregation.collect(start_time, end_time) + describe '#update' do + describe 'when number data point does not exist' do + it 'creates a new one and set increment' do + sum = build_sum + + sum.update(5, { 'service' => 'aaa' }) + sum.update(1, { 'service' => 'bbb' }) + + number_data_points = sum.collect(start_time_unix_nano, end_time_unix_nano) + assert(number_data_points.size == 2) - sum_aggregation.update(1, {}) - # Assert that the recent update does not - # impact the already collected metrics - _(ndps[0].value).must_equal(3) + assert(number_data_points[0].value == 5) + assert(number_data_points[0].attributes == { 'service' => 'aaa' }) - ndps = sum_aggregation.collect(start_time, end_time) - # Assert that we are not accumulating values - # between calls to collect - _(ndps[0].value).must_equal(1) + assert(number_data_points[1].value == 1) + assert(number_data_points[1].attributes == { 'service' => 'bbb' }) + end + end + + describe 'when number data point exists' do + it 'updates the existing one adding increment' do + sum = build_sum + + sum.update(5, { 'service' => 'aaa' }) + sum.update(1, { 'service' => 'bbb' }) + sum.update(10, { 'service' => 'aaa' }) + + number_data_points = sum.collect(start_time_unix_nano, end_time_unix_nano) + assert(number_data_points.size == 2) + + assert(number_data_points[0].value == 15) + assert(number_data_points[0].attributes == { 'service' => 'aaa' }) + + assert(number_data_points[1].value == 1) + assert(number_data_points[1].attributes == { 'service' => 'bbb' }) + end + end end - describe 'when aggregation_temporality is not delta' do - let(:aggregation_temporality) { :not_delta } + describe '#collect' do + describe 'when aggregation_temporality is :delta' do + it'sets timestamps, returns array of number data points and clears, not aggregating between calls' do + sum = build_sum(aggregation_temporality: :delta) + + sum.update(5, { 'service' => 'aaa' }) + sum.update(1, { 'service' => 'bbb' }) + + number_data_points = sum.collect(start_time_unix_nano, end_time_unix_nano) + assert(number_data_points.size == 2) + + assert(number_data_points[0].value == 5) + assert(number_data_points[0].attributes == { 'service' => 'aaa' }) + assert(number_data_points[0].start_time_unix_nano == start_time_unix_nano) + assert(number_data_points[0].time_unix_nano == end_time_unix_nano) - it 'allows metrics to accumulate' do - sum_aggregation.update(1, {}) - sum_aggregation.update(2, {}) - ndps = sum_aggregation.collect(start_time, end_time) + assert(number_data_points[1].value == 1) + assert(number_data_points[1].attributes == { 'service' => 'bbb' }) + assert(number_data_points[1].start_time_unix_nano == start_time_unix_nano) + assert(number_data_points[1].time_unix_nano == end_time_unix_nano) - sum_aggregation.update(1, {}) - # Assert that the recent update does not - # impact the already collected metrics - _(ndps[0].value).must_equal(3) + new_start_time_unix_nano = start_time_unix_nano + 10*(10**9) + new_end_time_unix_nano = end_time_unix_nano + 10*(10**9) - ndps = sum_aggregation.collect(start_time, end_time) - # Assert that we are accumulating values - # and not just capturing the delta since - # the previous collect call - _(ndps[0].value).must_equal(4) + number_data_points = sum.collect(new_start_time_unix_nano, new_end_time_unix_nano) + assert(number_data_points.empty?) + + sum.update(10, { 'service' => 'aaa' }) + + number_data_points = sum.collect(new_start_time_unix_nano, new_end_time_unix_nano) + assert(number_data_points.size == 1) + + assert(number_data_points[0].value == 10) + assert(number_data_points[0].attributes == { 'service' => 'aaa' }) + assert(number_data_points[0].start_time_unix_nano == new_start_time_unix_nano) + assert(number_data_points[0].time_unix_nano == new_end_time_unix_nano) + + number_data_points = sum.collect(new_start_time_unix_nano, new_end_time_unix_nano) + assert(number_data_points.empty?) + end end + + describe 'when aggregation_temporality is not :delta' do + it 'sets timestamps, returns array of number data points but does not clear, aggregating between calls' do + sum = build_sum(aggregation_temporality: :anything) + + sum.update(5, { 'service' => 'aaa' }) + sum.update(1, { 'service' => 'bbb' }) + + number_data_points = sum.collect(start_time_unix_nano, end_time_unix_nano) + assert(number_data_points.size == 2) + + assert(number_data_points[0].value == 5) + assert(number_data_points[0].attributes == { 'service' => 'aaa' }) + assert(number_data_points[0].start_time_unix_nano == start_time_unix_nano) + assert(number_data_points[0].time_unix_nano == end_time_unix_nano) + + assert(number_data_points[1].value == 1) + assert(number_data_points[1].attributes == { 'service' => 'bbb' }) + assert(number_data_points[1].start_time_unix_nano == start_time_unix_nano) + assert(number_data_points[1].time_unix_nano == end_time_unix_nano) + + sum.update(10, { 'service' => 'aaa' }) + sum.update(3, { 'service' => 'ccc' }) + + new_start_time_unix_nano = start_time_unix_nano + 10*(10**9) + new_end_time_unix_nano = end_time_unix_nano + 10*(10**9) + + number_data_points = sum.collect(new_start_time_unix_nano, new_end_time_unix_nano) + assert(number_data_points.size == 3) + + assert(number_data_points[0].value == 15) + assert(number_data_points[0].attributes == { 'service' => 'aaa' }) + assert(number_data_points[0].start_time_unix_nano == start_time_unix_nano) + assert(number_data_points[0].time_unix_nano == new_end_time_unix_nano) + + assert(number_data_points[1].value == 1) + assert(number_data_points[1].attributes == { 'service' => 'bbb' }) + assert(number_data_points[1].start_time_unix_nano == start_time_unix_nano) + assert(number_data_points[1].time_unix_nano == new_end_time_unix_nano) + + assert(number_data_points[2].value == 3) + assert(number_data_points[2].attributes == { 'service' => 'ccc' }) + assert(number_data_points[2].start_time_unix_nano == new_start_time_unix_nano) # new start time + assert(number_data_points[2].time_unix_nano == new_end_time_unix_nano) + + number_data_points = sum.collect(new_start_time_unix_nano, new_end_time_unix_nano) + assert(number_data_points.size == 3) + end + end + end + + def now_in_nano + (Time.now.to_r * 1_000_000_000).to_i + end + + def build_sum(**kwargs) + OpenTelemetry::SDK::Metrics::Aggregation::Sum.new(**kwargs) end end diff --git a/metrics_sdk/test/opentelemetry/sdk/metrics/meter_provider_test.rb b/metrics_sdk/test/opentelemetry/sdk/metrics/meter_provider_test.rb index f781d613b3..aa010d34ed 100644 --- a/metrics_sdk/test/opentelemetry/sdk/metrics/meter_provider_test.rb +++ b/metrics_sdk/test/opentelemetry/sdk/metrics/meter_provider_test.rb @@ -14,20 +14,38 @@ describe '#meter' do it 'requires a meter name' do - _(-> { OpenTelemetry.meter_provider.meter }).must_raise(ArgumentError) + _(-> { build_meter_provider.meter }).must_raise(ArgumentError) end it 'creates a new meter' do - meter = OpenTelemetry.meter_provider.meter('test') + meter = build_meter_provider.meter('test-meter') _(meter).must_be_instance_of(OpenTelemetry::SDK::Metrics::Meter) end - it 'repeated calls does not recreate a meter of the same name' do - meter_a = OpenTelemetry.meter_provider.meter('test') - meter_b = OpenTelemetry.meter_provider.meter('test') + it 'repeated calls do not recreate a meter of the same name' do + meter_provider = build_meter_provider - _(meter_a).must_equal(meter_b) + meter_a = meter_provider.meter('test-meter') + meter_b = meter_provider.meter('test-meter') + + _(meter_a.object_id).must_equal(meter_b.object_id) + end + + describe 'when meter_provider is shutdown' do + it 'returns a noop meter from API and logs a message' do + OpenTelemetry::TestHelpers.with_test_logger do |log_stream| + meter_provider = build_meter_provider + meter_provider.shutdown + + meter = meter_provider.meter('test-meter') + + assert(meter.instance_of?(OpenTelemetry::Metrics::Meter)) + assert(log_stream.string.match?( + /calling MeterProvider#meter after shutdown, a noop meter will be returned./ + )) + end + end end end @@ -40,15 +58,6 @@ end end - it 'returns a no-op meter after being shutdown' do - with_test_logger do |log_stream| - OpenTelemetry.meter_provider.shutdown - - _(OpenTelemetry.meter_provider.meter('test')).must_be_instance_of(OpenTelemetry::Metrics::Meter) - _(log_stream.string).must_match(/calling MeterProvider#meter after shutdown, a noop meter will be returned/) - end - end - it 'returns a timeout response when it times out' do mock_metric_reader = new_mock_reader mock_metric_reader.expect(:nothing_gets_called_because_it_times_out_first, nil) @@ -98,7 +107,7 @@ describe '#add_metric_reader' do it 'adds a metric reader' do - metric_reader = OpenTelemetry::SDK::Metrics::Export::MetricReader.new + metric_reader = build_metric_reader OpenTelemetry.meter_provider.add_metric_reader(metric_reader) @@ -106,41 +115,49 @@ end it 'associates the metric store with instruments created before the metric reader' do - meter_a = OpenTelemetry.meter_provider.meter('a').create_counter('meter_a') + instrument = OpenTelemetry.meter_provider.meter('test-meter').create_counter('test-instrument') - metric_reader_a = OpenTelemetry::SDK::Metrics::Export::MetricReader.new + metric_reader_a = build_metric_reader OpenTelemetry.meter_provider.add_metric_reader(metric_reader_a) - metric_reader_b = OpenTelemetry::SDK::Metrics::Export::MetricReader.new + metric_reader_b = build_metric_reader OpenTelemetry.meter_provider.add_metric_reader(metric_reader_b) - _(meter_a.instance_variable_get(:@metric_streams).size).must_equal(2) + _(instrument.instance_variable_get(:@metric_streams).size).must_equal(2) _(metric_reader_a.metric_store.instance_variable_get(:@metric_streams).size).must_equal(1) _(metric_reader_b.metric_store.instance_variable_get(:@metric_streams).size).must_equal(1) end it 'associates the metric store with instruments created after the metric reader' do - metric_reader_a = OpenTelemetry::SDK::Metrics::Export::MetricReader.new + metric_reader_a = build_metric_reader OpenTelemetry.meter_provider.add_metric_reader(metric_reader_a) - metric_reader_b = OpenTelemetry::SDK::Metrics::Export::MetricReader.new + metric_reader_b = build_metric_reader OpenTelemetry.meter_provider.add_metric_reader(metric_reader_b) - meter_a = OpenTelemetry.meter_provider.meter('a').create_counter('meter_a') + instrument = OpenTelemetry.meter_provider.meter('test-meter').create_counter('test-instrument') - _(meter_a.instance_variable_get(:@metric_streams).size).must_equal(2) + _(instrument.instance_variable_get(:@metric_streams).size).must_equal(2) _(metric_reader_a.metric_store.instance_variable_get(:@metric_streams).size).must_equal(1) _(metric_reader_b.metric_store.instance_variable_get(:@metric_streams).size).must_equal(1) end end - # TODO: OpenTelemetry.meter_provider.add_view - describe '#add_view' do - end + # # TODO: OpenTelemetry.meter_provider.add_view + # describe '#add_view' do + # end private def new_mock_reader Minitest::Mock.new(OpenTelemetry::SDK::Metrics::Export::MetricReader.new) end + + def build_metric_reader + OpenTelemetry::SDK::Metrics::Export::MetricReader.new + end + + def build_meter_provider + OpenTelemetry::SDK::Metrics::MeterProvider.new + end end diff --git a/metrics_sdk/test/opentelemetry/sdk/metrics/meter_test.rb b/metrics_sdk/test/opentelemetry/sdk/metrics/meter_test.rb index 6a6cba2fdc..b4fa39e338 100644 --- a/metrics_sdk/test/opentelemetry/sdk/metrics/meter_test.rb +++ b/metrics_sdk/test/opentelemetry/sdk/metrics/meter_test.rb @@ -9,53 +9,129 @@ describe OpenTelemetry::SDK::Metrics::Meter do before { OpenTelemetry::SDK.configure } - let(:meter) { OpenTelemetry.meter_provider.meter('new_meter') } + describe '#instrumentation_scope' do + it 'creates an instrumentation_scope' do + meter = build_meter( + 'test-meter', + version: '1.0.0', + schema_url: 'https://example.com/schema/1.0.0', + attributes: { 'key' => 'value' } + ) + + assert(meter.instrumentation_scope.instance_of?(OpenTelemetry::SDK::InstrumentationScope)) + assert(meter.instrumentation_scope.name == 'test-meter') + assert(meter.instrumentation_scope.version == '1.0.0') + assert(meter.instrumentation_scope.schema_url == 'https://example.com/schema/1.0.0') + assert(meter.instrumentation_scope.attributes == { 'key' => 'value' }) + end + end describe '#create_counter' do it 'creates a counter instrument' do - instrument = meter.create_counter('a_counter', unit: 'minutes', description: 'useful description') - _(instrument).must_be_instance_of OpenTelemetry::SDK::Metrics::Instrument::Counter + instrument = build_meter.create_counter( + 'test-instrument', + unit: 'b', + description: 'bytes received', + advice: { some: { value: 123 }} + ) + + assert(instrument.instance_of?(OpenTelemetry::SDK::Metrics::Instrument::Counter)) + assert(instrument.name == 'test-instrument') + assert(instrument.unit == 'b') + assert(instrument.description == 'bytes received') + assert(instrument.advice == { some: { value: 123 }}) end end describe '#create_histogram' do it 'creates a histogram instrument' do - instrument = meter.create_histogram('a_histogram', unit: 'minutes', description: 'useful description') - _(instrument).must_be_instance_of OpenTelemetry::SDK::Metrics::Instrument::Histogram + instrument = build_meter.create_histogram( + 'test-instrument', + unit: 'seconds', + description: 'request duration', + advice: { some: { value: 123 }} + ) + + assert(instrument.instance_of?(OpenTelemetry::SDK::Metrics::Instrument::Histogram)) + assert(instrument.name == 'test-instrument') + assert(instrument.unit == 'seconds') + assert(instrument.description == 'request duration') + assert(instrument.advice == { some: { value: 123 }}) end end describe '#create_up_down_counter' do - it 'creates a up_down_counter instrument' do - instrument = meter.create_up_down_counter('a_up_down_counter', unit: 'minutes', description: 'useful description') - _(instrument).must_be_instance_of OpenTelemetry::SDK::Metrics::Instrument::UpDownCounter + it 'creates an up_down_counter instrument' do + instrument = build_meter.create_up_down_counter( + 'test-instrument', + unit: 'jobs', + description: 'number of jobs in a queue', + advice: { some: { value: 123 }} + ) + + assert(instrument.instance_of?(OpenTelemetry::SDK::Metrics::Instrument::UpDownCounter)) + assert(instrument.name == 'test-instrument') + assert(instrument.unit == 'jobs') + assert(instrument.description == 'number of jobs in a queue') + assert(instrument.advice == { some: { value: 123 }}) end end describe '#create_observable_counter' do - it 'creates a observable_counter instrument' do - # TODO: Implement observable instruments - skip - instrument = meter.create_observable_counter('a_observable_counter', unit: 'minutes', description: 'useful description', callback: nil) - _(instrument).must_be_instance_of OpenTelemetry::SDK::Metrics::Instrument::ObservableCounter + it 'creates an observable_counter instrument' do + callback = ->{} + instrument = build_meter.create_observable_counter( + 'test-instrument', + unit: 'faults', + description: 'number of page faults', + callbacks: [callback] + ) + + assert(instrument.instance_of?(OpenTelemetry::SDK::Metrics::Instrument::ObservableCounter)) + assert(instrument.name == 'test-instrument') + assert(instrument.unit == 'faults') + assert(instrument.description == 'number of page faults') + assert(instrument.callbacks == [callback]) end end describe '#create_observable_gauge' do - it 'creates a observable_gauge instrument' do - # TODO: Implement observable instruments - skip - instrument = meter.create_observable_gauge('a_observable_gauge', unit: 'minutes', description: 'useful description', callback: nil) - _(instrument).must_be_instance_of OpenTelemetry::SDK::Metrics::Instrument::ObservableGauge + it 'creates an observable_gauge instrument' do + callback = ->{} + instrument = build_meter.create_observable_gauge( + 'test-instrument', + unit: 'celsius', + description: 'room temperature', + callbacks: [callback] + ) + + assert(instrument.instance_of?(OpenTelemetry::SDK::Metrics::Instrument::ObservableGauge)) + assert(instrument.name == 'test-instrument') + assert(instrument.unit == 'celsius') + assert(instrument.description == 'room temperature') + assert(instrument.callbacks == [callback]) end end describe '#create_observable_up_down_counter' do - it 'creates a observable_up_down_counter instrument' do - # TODO: Implement observable instruments - skip - instrument = meter.create_observable_up_down_counter('a_observable_up_down_counter', unit: 'minutes', description: 'useful description', callback: nil) - _(instrument).must_be_instance_of OpenTelemetry::SDK::Metrics::Instrument::ObservableUpDownCounter + it 'creates an observable_up_down_counter instrument' do + callback = ->{} + instrument = build_meter.create_observable_up_down_counter( + 'test-instrument', + unit: 'b', + description: 'process heap size', + callbacks: [callback] + ) + + assert(instrument.instance_of?(OpenTelemetry::SDK::Metrics::Instrument::ObservableUpDownCounter)) + assert(instrument.name == 'test-instrument') + assert(instrument.unit == 'b') + assert(instrument.description == 'process heap size') + assert(instrument.callbacks == [callback]) end end + + def build_meter(name = 'test-meter', **kwargs) + OpenTelemetry::SDK::Metrics::Meter.new(name, **kwargs) + end end diff --git a/sdk/lib/opentelemetry/sdk/instrumentation_scope.rb b/sdk/lib/opentelemetry/sdk/instrumentation_scope.rb index 606738ebbc..413e6a7298 100644 --- a/sdk/lib/opentelemetry/sdk/instrumentation_scope.rb +++ b/sdk/lib/opentelemetry/sdk/instrumentation_scope.rb @@ -7,7 +7,11 @@ module OpenTelemetry module SDK # InstrumentationScope is a struct containing scope information for export. - InstrumentationScope = Struct.new(:name, - :version) + InstrumentationScope = Struct.new( + :name, + :version, + :schema_url, + :attributes + ) end end