diff --git a/metrics_api/.rubocop.yml b/metrics_api/.rubocop.yml index 12b6d47a32..5583aeb7e5 100644 --- a/metrics_api/.rubocop.yml +++ b/metrics_api/.rubocop.yml @@ -5,6 +5,7 @@ AllCops: Lint/UnusedMethodArgument: Enabled: false + Metrics/AbcSize: Enabled: false Metrics/LineLength: @@ -17,8 +18,14 @@ Metrics/CyclomaticComplexity: Max: 20 Metrics/ParameterLists: Enabled: false + +Naming/VariableNumber: + Enabled: false Naming/FileName: Exclude: - "lib/opentelemetry-metrics-api.rb" + +Style/IfUnlessModifier: + Enabled: false Style/ModuleFunction: Enabled: false diff --git a/metrics_api/lib/opentelemetry-metrics-api.rb b/metrics_api/lib/opentelemetry-metrics-api.rb index 7363a7561d..960fe3e088 100644 --- a/metrics_api/lib/opentelemetry-metrics-api.rb +++ b/metrics_api/lib/opentelemetry-metrics-api.rb @@ -29,6 +29,7 @@ def meter_provider=(provider) logger.debug("Upgrading default proxy meter provider to #{provider.class}") @meter_provider.delegate = provider end + @meter_provider = provider end end diff --git a/metrics_api/lib/opentelemetry/metrics/instrument.rb b/metrics_api/lib/opentelemetry/metrics/instrument.rb index 3782a8420c..f7ce18efad 100644 --- a/metrics_api/lib/opentelemetry/metrics/instrument.rb +++ b/metrics_api/lib/opentelemetry/metrics/instrument.rb @@ -4,12 +4,15 @@ # # SPDX-License-Identifier: Apache-2.0 +require 'opentelemetry/metrics/instrument/synchronous_instrument' require 'opentelemetry/metrics/instrument/counter' require 'opentelemetry/metrics/instrument/histogram' +require 'opentelemetry/metrics/instrument/up_down_counter' + +require 'opentelemetry/metrics/instrument/asynchronous_instrument' require 'opentelemetry/metrics/instrument/observable_counter' require 'opentelemetry/metrics/instrument/observable_gauge' require 'opentelemetry/metrics/instrument/observable_up_down_counter' -require 'opentelemetry/metrics/instrument/up_down_counter' module OpenTelemetry module Metrics diff --git a/metrics_api/lib/opentelemetry/metrics/instrument/asynchronous_instrument.rb b/metrics_api/lib/opentelemetry/metrics/instrument/asynchronous_instrument.rb new file mode 100644 index 0000000000..0d05809bab --- /dev/null +++ b/metrics_api/lib/opentelemetry/metrics/instrument/asynchronous_instrument.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +module OpenTelemetry + module Metrics + module Instrument + # https://opentelemetry.io/docs/specs/otel/metrics/api/#asynchronous-instrument-api + class AsynchronousInstrument + attr_reader :name, :unit, :description, :callbacks + + # @api private + def initialize(name, unit: nil, description: nil, callbacks: nil) + @name = name + @unit = unit || '' + @description = description || '' + @callbacks = callbacks ? Array(callbacks) : [] + end + + # @param callbacks [Proc, Array] + # Callback functions should: + # - be reentrant safe; + # - not take an indefinite amount of time; + # - not make duplicate observations (more than one Measurement with the same attributes) + # across all registered callbacks; + def register_callbacks(*callbacks); end + + # @param callbacks [Proc, Array] + # Callback functions should: + # - be reentrant safe; + # - not take an indefinite amount of time; + # - not make duplicate observations (more than one Measurement with the same attributes) + # across all registered callbacks; + def unregister_callbacks(*callbacks); end + end + end + end +end diff --git a/metrics_api/lib/opentelemetry/metrics/instrument/counter.rb b/metrics_api/lib/opentelemetry/metrics/instrument/counter.rb index 870364ec89..e3ac20c278 100644 --- a/metrics_api/lib/opentelemetry/metrics/instrument/counter.rb +++ b/metrics_api/lib/opentelemetry/metrics/instrument/counter.rb @@ -8,10 +8,10 @@ module OpenTelemetry module Metrics module Instrument # No-op implementation of Counter. - class Counter + class Counter < SynchronousInstrument # Increment the Counter by a fixed amount. # - # @param [numeric] increment The increment amount, which MUST be a non-negative numeric value. + # @param [Numeric] increment The increment amount, which MUST be a non-negative numeric value. # @param [Hash{String => String, Numeric, Boolean, Array}] attributes # Values must be non-nil and (array of) string, boolean or numeric type. # Array values must not contain nil elements and all elements must be of diff --git a/metrics_api/lib/opentelemetry/metrics/instrument/histogram.rb b/metrics_api/lib/opentelemetry/metrics/instrument/histogram.rb index 1de448baab..1b514094e0 100644 --- a/metrics_api/lib/opentelemetry/metrics/instrument/histogram.rb +++ b/metrics_api/lib/opentelemetry/metrics/instrument/histogram.rb @@ -8,10 +8,10 @@ module OpenTelemetry module Metrics module Instrument # No-op implementation of Histogram. - class Histogram + class Histogram < SynchronousInstrument # Updates the statistics with the specified amount. # - # @param [numeric] amount The amount of the Measurement, which MUST be a non-negative numeric value. + # @param [Numeric] amount The amount of the Measurement, which MUST be a non-negative numeric value. # @param [Hash{String => String, Numeric, Boolean, Array}] attributes # Values must be non-nil and (array of) string, boolean or numeric type. # Array values must not contain nil elements and all elements must be of diff --git a/metrics_api/lib/opentelemetry/metrics/instrument/observable_counter.rb b/metrics_api/lib/opentelemetry/metrics/instrument/observable_counter.rb index 15d8a6784b..34d63d3969 100644 --- a/metrics_api/lib/opentelemetry/metrics/instrument/observable_counter.rb +++ b/metrics_api/lib/opentelemetry/metrics/instrument/observable_counter.rb @@ -8,8 +8,7 @@ module OpenTelemetry module Metrics module Instrument # No-op implementation of ObservableCounter. - class ObservableCounter - # TODO + class ObservableCounter < AsynchronousInstrument 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 03606baf20..a604775959 100644 --- a/metrics_api/lib/opentelemetry/metrics/instrument/observable_gauge.rb +++ b/metrics_api/lib/opentelemetry/metrics/instrument/observable_gauge.rb @@ -8,8 +8,7 @@ module OpenTelemetry module Metrics module Instrument # No-op implementation of ObservableGauge. - class ObservableGauge - # TODO + class ObservableGauge < AsynchronousInstrument 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 eb55887019..8f685906c0 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 @@ -8,8 +8,7 @@ module OpenTelemetry module Metrics module Instrument # No-op implementation of ObservableUpDownCounter. - class ObservableUpDownCounter - # TODO + class ObservableUpDownCounter < AsynchronousInstrument 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 new file mode 100644 index 0000000000..7452fec049 --- /dev/null +++ b/metrics_api/lib/opentelemetry/metrics/instrument/synchronous_instrument.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +module OpenTelemetry + module Metrics + module Instrument + # https://opentelemetry.io/docs/specs/otel/metrics/api/#synchronous-instrument-api + class SynchronousInstrument + attr_reader :name, :unit, :description, :advice + + # @api private + def initialize(name, unit: nil, description: nil, advice: nil) + @name = name + @unit = unit || '' + @description = description || '' + @advice = advice || {} + 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 f9793d1508..dc90493504 100644 --- a/metrics_api/lib/opentelemetry/metrics/instrument/up_down_counter.rb +++ b/metrics_api/lib/opentelemetry/metrics/instrument/up_down_counter.rb @@ -8,7 +8,7 @@ module OpenTelemetry module Metrics module Instrument # No-op implementation of UpDownCounter. - class UpDownCounter + class UpDownCounter < SynchronousInstrument # 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 846424995b..37616415fe 100644 --- a/metrics_api/lib/opentelemetry/metrics/meter.rb +++ b/metrics_api/lib/opentelemetry/metrics/meter.rb @@ -8,71 +8,162 @@ module OpenTelemetry module Metrics # No-op implementation of Meter. class Meter - COUNTER = Instrument::Counter.new - OBSERVABLE_COUNTER = Instrument::ObservableCounter.new - HISTOGRAM = Instrument::Histogram.new - OBSERVABLE_GAUGE = Instrument::ObservableGauge.new - UP_DOWN_COUNTER = Instrument::UpDownCounter.new - OBSERVABLE_UP_DOWN_COUNTER = Instrument::ObservableUpDownCounter.new + NOOP_COUNTER = Instrument::Counter.new('no-op') + NOOP_HISTOGRAM = Instrument::Histogram.new('no-op') + NOOP_UP_DOWN_COUNTER = Instrument::UpDownCounter.new('no-op') + NOOP_OBSERVABLE_COUNTER = Instrument::ObservableCounter.new('no-op') + NOOP_OBSERVABLE_GAUGE = Instrument::ObservableGauge.new('no-op') + NOOP_OBSERVABLE_UP_DOWN_COUNTER = Instrument::ObservableUpDownCounter.new('no-op') + + private_constant( + :NOOP_COUNTER, + :NOOP_HISTOGRAM, + :NOOP_UP_DOWN_COUNTER, + :NOOP_OBSERVABLE_COUNTER, + :NOOP_OBSERVABLE_GAUGE, + :NOOP_OBSERVABLE_UP_DOWN_COUNTER + ) + + attr_reader :name, :version, :schema_url, :attributes + + # @api private + def initialize(name, version: nil, schema_url: nil, attributes: nil) + @name = name + @version = version || '' + @schema_url = schema_url || '' + @attributes = attributes || {} - NAME_REGEX = /^[a-zA-Z][-.\w]{0,62}$/ - - private_constant(:COUNTER, :OBSERVABLE_COUNTER, :HISTOGRAM, :OBSERVABLE_GAUGE, :UP_DOWN_COUNTER, :OBSERVABLE_UP_DOWN_COUNTER) - - DuplicateInstrumentError = Class.new(OpenTelemetry::Error) - InstrumentNameError = Class.new(OpenTelemetry::Error) - InstrumentUnitError = Class.new(OpenTelemetry::Error) - InstrumentDescriptionError = Class.new(OpenTelemetry::Error) - - def initialize @mutex = Mutex.new @instrument_registry = {} end - def create_counter(name, unit: nil, description: nil) - create_instrument(:counter, name, unit, description, nil) { COUNTER } + # @param name [String] + # Must conform to the instrument name syntax: + # not nil or empty, case-insensitive ASCII string that matches `/\A[a-z][a-z0-9_.-]{0,62}\z/i` + # @param unit [optional String] + # Must conform to the instrument unit rule: + # case-sensitive ASCII string with maximum length of 63 characters + # @param description [optional String] + # Must conform to the instrument description rule: + # UTF-8 string but up to 3 bytes per charater with maximum length of 1023 characters + # @param advice [optional Hash] Set of recommendations aimed at assisting + # implementations in providing useful output with minimal configuration + # + # @return [Instrument::Counter] + def create_counter(name, unit: nil, description: nil, advice: nil) + create_instrument(:counter, name, unit, description, advice, nil) { NOOP_COUNTER } end - def create_histogram(name, unit: nil, description: nil) - create_instrument(:histogram, name, unit, description, nil) { HISTOGRAM } + # @param name [String] + # Must conform to the instrument name syntax: + # not nil or empty, case-insensitive ASCII string that matches `/\A[a-z][a-z0-9_.-]{0,62}\z/i` + # @param unit [optional String] + # Must conform to the instrument unit rule: + # case-sensitive ASCII string with maximum length of 63 characters + # @param description [optional String] + # Must conform to the instrument description rule: + # UTF-8 string but up to 3 bytes per charater with maximum length of 1023 characters + # @param advice [optional Hash] Set of recommendations aimed at assisting + # implementations in providing useful output with minimal configuration + # + # @return [Instrument::Histogram] + def create_histogram(name, unit: nil, description: nil, advice: nil) + create_instrument(:histogram, name, unit, description, advice, nil) { NOOP_HISTOGRAM } end - def create_up_down_counter(name, unit: nil, description: nil) - create_instrument(:up_down_counter, name, unit, description, nil) { UP_DOWN_COUNTER } + # @param name [String] + # Must conform to the instrument name syntax: + # not nil or empty, case-insensitive ASCII string that matches `/\A[a-z][a-z0-9_.-]{0,62}\z/i` + # @param unit [optional String] + # Must conform to the instrument unit rule: + # case-sensitive ASCII string with maximum length of 63 characters + # @param description [optional String] + # Must conform to the instrument description rule: + # UTF-8 string but up to 3 bytes per charater with maximum length of 1023 characters + # @param advice [optional Hash] Set of recommendations aimed at assisting + # implementations in providing useful output with minimal configuration + # + # @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 } end - def create_observable_counter(name, callback:, unit: nil, description: nil) - create_instrument(:observable_counter, name, unit, description, callback) { OBSERVABLE_COUNTER } + # @param name [String] + # Must conform to the instrument name syntax: + # not nil or empty, case-insensitive ASCII string that matches `/\A[a-z][a-z0-9_.-]{0,62}\z/i` + # @param unit [optional String] + # Must conform to the instrument unit rule: + # case-sensitive ASCII string with maximum length of 63 characters + # @param description [optional String] + # Must conform to the instrument description rule: + # UTF-8 string but up to 3 bytes per charater with maximum length of 1023 characters + # @param callbacks [optional Proc, Array] + # Callback functions should: + # - be reentrant safe; + # - not take an indefinite amount of time; + # - not make duplicate observations (more than one Measurement with the same attributes) + # across all registered callbacks; + # + # @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 } end - def create_observable_gauge(name, callback:, unit: nil, description: nil) - create_instrument(:observable_gauge, name, unit, description, callback) { OBSERVABLE_GAUGE } + # @param name [String] + # Must conform to the instrument name syntax: + # not nil or empty, case-insensitive ASCII string that matches `/\A[a-z][a-z0-9_.-]{0,62}\z/i` + # @param unit [optional String] + # Must conform to the instrument unit rule: + # case-sensitive ASCII string with maximum length of 63 characters + # @param description [optional String] + # Must conform to the instrument description rule: + # UTF-8 string but up to 3 bytes per charater with maximum length of 1023 characters + # @param callbacks [optional Proc, Array] + # Callback functions should: + # - be reentrant safe; + # - not take an indefinite amount of time; + # - not make duplicate observations (more than one Measurement with the same attributes) + # across all registered callbacks; + # + # @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 } end - def create_observable_up_down_counter(name, callback:, unit: nil, description: nil) - create_instrument(:observable_up_down_counter, name, unit, description, callback) { OBSERVABLE_UP_DOWN_COUNTER } + # @param name [String] + # Must conform to the instrument name syntax: + # not nil or empty, case-insensitive ASCII string that matches `/\A[a-z][a-z0-9_.-]{0,62}\z/i` + # @param unit [optional String] + # Must conform to the instrument unit rule: + # case-sensitive ASCII string with maximum length of 63 characters + # @param description [optional String] + # Must conform to the instrument description rule: + # UTF-8 string but up to 3 bytes per charater with maximum length of 1023 characters + # @param callbacks [optional Proc, Array] + # Callback functions should: + # - be reentrant safe; + # - not take an indefinite amount of time; + # - not make duplicate observations (more than one Measurement with the same attributes) + # across all registered callbacks; + # + # @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 } end private - def create_instrument(kind, name, unit, description, callback) - raise InstrumentNameError if name.nil? - raise InstrumentNameError if name.empty? - raise InstrumentNameError unless NAME_REGEX.match?(name) - raise InstrumentUnitError if unit && (!unit.ascii_only? || unit.size > 63) - raise InstrumentDescriptionError if description && (description.size > 1023 || !utf8mb3_encoding?(description.dup)) + def create_instrument(kind, name, unit, description, advice, callbacks) + name = name.downcase @mutex.synchronize do - OpenTelemetry.logger.warn("duplicate instrument registration occurred for instrument #{name}") if @instrument_registry.include? name + if @instrument_registry.include?(name) + OpenTelemetry.logger.warn("duplicate instrument registration occurred for #{name}") + end @instrument_registry[name] = yield end end - - def utf8mb3_encoding?(string) - string.force_encoding('UTF-8').valid_encoding? && - string.each_char { |c| return false if c.bytesize >= 4 } - end end end end diff --git a/metrics_api/lib/opentelemetry/metrics/meter_provider.rb b/metrics_api/lib/opentelemetry/metrics/meter_provider.rb index 006f9ba03c..3cd53b110c 100644 --- a/metrics_api/lib/opentelemetry/metrics/meter_provider.rb +++ b/metrics_api/lib/opentelemetry/metrics/meter_provider.rb @@ -8,14 +8,32 @@ module OpenTelemetry module Metrics # No-op implementation of a meter provider. class MeterProvider - # Returns a {Meter} instance. - # - # @param [String] name Instrumentation package name - # @param [optional String] version Instrumentation package version + NOOP_METER = Meter.new('no-op') + + private_constant :NOOP_METER + + def initialize(resource: nil) + @resource = resource + + @mutex = Mutex.new + @meter_registry = {} + @stopped = false + @metric_readers = [] + end + + # @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) - @meter ||= Meter.new + # @return [Metrics::Meter] + def meter(name, version: nil, schema_url: nil, attributes: nil) + NOOP_METER end end end diff --git a/metrics_api/test/opentelemetry/metrics/instrument/asynchronous_instrument_test.rb b/metrics_api/test/opentelemetry/metrics/instrument/asynchronous_instrument_test.rb new file mode 100644 index 0000000000..12e134c857 --- /dev/null +++ b/metrics_api/test/opentelemetry/metrics/instrument/asynchronous_instrument_test.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'test_helper' + +describe OpenTelemetry::Metrics::Instrument::AsynchronousInstrument do + describe '#name' do + it 'returns name' do + instrument = build_asynchronous_instrument('test-instrument') + + assert(instrument.name == 'test-instrument') + end + end + + describe '#unit' do + it 'returns unit' do + instrument = build_asynchronous_instrument('test-instrument') + assert(instrument.unit == '') + + instrument = build_asynchronous_instrument('test-instrument', unit: 'celsius') + assert(instrument.unit == 'celsius') + end + end + + describe '#description' do + it 'returns description' do + instrument = build_asynchronous_instrument('test-instrument') + assert(instrument.description == '') + + instrument = build_asynchronous_instrument('test-instrument', description: 'room temperature') + assert(instrument.description == 'room temperature') + end + end + + describe '#callbacks' do + it 'returns callbacks list' do + instrument = build_asynchronous_instrument('test-instrument') + assert(instrument.callbacks == []) + + callback_1 = -> {} + callback_2 = -> {} + + instrument = build_asynchronous_instrument('test-instrument', callbacks: callback_1) + assert(instrument.callbacks == [callback_1]) + + instrument = build_asynchronous_instrument('test-instrument', callbacks: [callback_1, callback_2]) + assert(instrument.callbacks == [callback_1, callback_2]) + end + end + + describe '#register_callbacks' do + it 'responds without errors and returns nil' do + instrument = build_asynchronous_instrument('test-instrument') + + assert(instrument.register_callbacks(-> {}).nil?) + assert(instrument.register_callbacks(-> {}, -> {}).nil?) + assert(instrument.register_callbacks([-> {}, -> {}]).nil?) + end + end + + describe '#unregister_callbacks' do + it 'responds without errors and returns nil' do + instrument = build_asynchronous_instrument('test-instrument') + + assert(instrument.unregister_callbacks(-> {}).nil?) + assert(instrument.unregister_callbacks(-> {}, -> {}).nil?) + assert(instrument.unregister_callbacks([-> {}, -> {}]).nil?) + end + end + + def build_asynchronous_instrument(*args, **kwargs) + OpenTelemetry::Metrics::Instrument::AsynchronousInstrument.new(*args, **kwargs) + end +end diff --git a/metrics_api/test/opentelemetry/metrics/instrument/counter_test.rb b/metrics_api/test/opentelemetry/metrics/instrument/counter_test.rb new file mode 100644 index 0000000000..f575988193 --- /dev/null +++ b/metrics_api/test/opentelemetry/metrics/instrument/counter_test.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'test_helper' + +describe OpenTelemetry::Metrics::Instrument::Counter do + describe '#add' do + it 'responds without errors and returns nil' do + counter = build_counter('test-instrument') + + assert(counter.add(1).nil?) + assert(counter.add(1, attributes: nil).nil?) + assert(counter.add(1, attributes: {}).nil?) + assert(counter.add(1, attributes: { 'key' => 'value' }).nil?) + end + end + + def build_counter(*args, **kwargs) + OpenTelemetry::Metrics::Instrument::Counter.new(*args, **kwargs) + end +end diff --git a/metrics_api/test/opentelemetry/metrics/instrument/histogram_test.rb b/metrics_api/test/opentelemetry/metrics/instrument/histogram_test.rb new file mode 100644 index 0000000000..3dea4ed838 --- /dev/null +++ b/metrics_api/test/opentelemetry/metrics/instrument/histogram_test.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'test_helper' + +describe OpenTelemetry::Metrics::Instrument::Histogram do + describe '#record' do + it 'responds without errors and returns nil' do + histogram = build_histogram('test-instrument') + + assert(histogram.record(1).nil?) + assert(histogram.record(1, attributes: nil).nil?) + assert(histogram.record(1, attributes: {}).nil?) + assert(histogram.record(1, attributes: { 'key' => 'value' }).nil?) + end + end + + def build_histogram(*args, **kwargs) + OpenTelemetry::Metrics::Instrument::Histogram.new(*args, **kwargs) + end +end diff --git a/metrics_api/test/opentelemetry/metrics/instrument/synchronous_instrument_test.rb b/metrics_api/test/opentelemetry/metrics/instrument/synchronous_instrument_test.rb new file mode 100644 index 0000000000..d61586b431 --- /dev/null +++ b/metrics_api/test/opentelemetry/metrics/instrument/synchronous_instrument_test.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'test_helper' + +describe OpenTelemetry::Metrics::Instrument::SynchronousInstrument do + describe '#name' do + it 'returns name' do + instrument = build_synchronous_instrument('test-instrument') + + assert(instrument.name == 'test-instrument') + end + end + + describe '#unit' do + it 'returns unit' do + instrument = build_synchronous_instrument('test-instrument') + assert(instrument.unit == '') + + instrument = build_synchronous_instrument('test-instrument', unit: 'b') + assert(instrument.unit == 'b') + end + end + + describe '#description' do + it 'returns description' do + instrument = build_synchronous_instrument('test-instrument') + assert(instrument.description == '') + + instrument = build_synchronous_instrument('test-instrument', description: 'bytes received') + assert(instrument.description == 'bytes received') + end + end + + describe '#advice' do + it 'returns advice' do + instrument = build_synchronous_instrument('test-instrument') + assert(instrument.advice == {}) + + instrument = build_synchronous_instrument( + 'test-instrument', + advice: { + histogram: { + explicit_bucket_boundaries: [0.1, 0.5, 1.0, 5.0, 10.0, 25.0, Float::INFINITY] + } + } + ) + assert(instrument.advice == { + histogram: { + explicit_bucket_boundaries: [0.1, 0.5, 1.0, 5.0, 10.0, 25.0, Float::INFINITY] + } + }) + end + end + + def build_synchronous_instrument(*args, **kwargs) + OpenTelemetry::Metrics::Instrument::SynchronousInstrument.new(*args, **kwargs) + end +end diff --git a/metrics_api/test/opentelemetry/metrics/instrument/up_down_counter_test.rb b/metrics_api/test/opentelemetry/metrics/instrument/up_down_counter_test.rb new file mode 100644 index 0000000000..ceed21c92f --- /dev/null +++ b/metrics_api/test/opentelemetry/metrics/instrument/up_down_counter_test.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'test_helper' + +describe OpenTelemetry::Metrics::Instrument::UpDownCounter do + describe '#add' do + it 'responds without errors and returns nil' do + up_down_counter = build_up_down_counter('test-instrument') + + assert(up_down_counter.add(1).nil?) + assert(up_down_counter.add(1, attributes: nil).nil?) + assert(up_down_counter.add(1, attributes: {}).nil?) + assert(up_down_counter.add(1, attributes: { 'key' => 'value' }).nil?) + end + end + + def build_up_down_counter(*args, **kwargs) + OpenTelemetry::Metrics::Instrument::UpDownCounter.new(*args, **kwargs) + end +end diff --git a/metrics_api/test/opentelemetry/metrics/meter_provider_test.rb b/metrics_api/test/opentelemetry/metrics/meter_provider_test.rb index 45b29677c3..fcbae01545 100644 --- a/metrics_api/test/opentelemetry/metrics/meter_provider_test.rb +++ b/metrics_api/test/opentelemetry/metrics/meter_provider_test.rb @@ -14,10 +14,19 @@ _(-> { meter_provider.meter }).must_raise(ArgumentError) end - it 'returns an instance of Meter' do + it 'returns an instance of Meter without errors' do meter_provider = build_meter_provider - assert(meter_provider.meter('test', version: '1.0.0').is_a?(OpenTelemetry::Metrics::Meter)) + meter = meter_provider.meter('test-meter') + assert(meter.is_a?(OpenTelemetry::Metrics::Meter)) + + meter = meter_provider.meter( + 'test-meter', + version: '1.0.0', + schema_url: 'https://example.com/schema/1.0.0', + attributes: { 'key' => 'value' } + ) + assert(meter.is_a?(OpenTelemetry::Metrics::Meter)) end end diff --git a/metrics_api/test/opentelemetry/metrics/meter_test.rb b/metrics_api/test/opentelemetry/metrics/meter_test.rb index 493c373b5b..59135a429f 100644 --- a/metrics_api/test/opentelemetry/metrics/meter_test.rb +++ b/metrics_api/test/opentelemetry/metrics/meter_test.rb @@ -7,67 +7,201 @@ require 'test_helper' describe OpenTelemetry::Metrics::Meter do - INSTRUMENT_NAME_ERROR = OpenTelemetry::Metrics::Meter::InstrumentNameError - INSTRUMENT_UNIT_ERROR = OpenTelemetry::Metrics::Meter::InstrumentUnitError - INSTRUMENT_DESCRIPTION_ERROR = OpenTelemetry::Metrics::Meter::InstrumentDescriptionError - DUPLICATE_INSTRUMENT_ERROR = OpenTelemetry::Metrics::Meter::DuplicateInstrumentError + describe '#name' do + it 'returns name' do + meter = build_meter('test-meter') - let(:meter_provider) { OpenTelemetry::Metrics::MeterProvider.new } - let(:meter) { meter_provider.meter('test-meter') } + assert(meter.name == 'test-meter') + end + end + + describe '#version' do + it 'returns version' do + meter = build_meter('test-meter') + assert(meter.version == '') + + meter = build_meter('test-meter', version: '1.0.0') + assert(meter.version == '1.0.0') + end + end + + describe '#schema_url' do + it 'returns schema_url' do + meter = build_meter('test-meter') + assert(meter.schema_url == '') + + meter = build_meter('test-meter', schema_url: 'https://example.com/schema/1.0.0') + assert(meter.schema_url == 'https://example.com/schema/1.0.0') + end + end + + describe '#attributes' do + it 'returns attributes' do + meter = build_meter('test-meter') + assert(meter.attributes == {}) + + meter = build_meter('test-meter', attributes: { 'key' => 'value' }) + assert(meter.attributes == { 'key' => 'value' }) + end + end + + describe '#create_counter' do + it 'creates and returns an instance of Counter instrument' do + meter = build_meter + + instrument = meter.create_counter( + 'test-instrument', + unit: 'b', + description: 'number of bytes received', + advice: {} + ) + + assert(instrument.is_a?(OpenTelemetry::Metrics::Instrument::Counter)) + end + + it 'logs a warning message when an instrument with the same name already exists' do + meter = build_meter - describe 'creating an instrument' do - it 'duplicate instrument registration logs a warning' do OpenTelemetry::TestHelpers.with_test_logger do |log_stream| - meter.create_counter('a_counter') - meter.create_counter('a_counter') - _(log_stream.string).must_match(/duplicate instrument registration occurred for instrument a_counter/) + meter.create_counter('test-instrument') + meter.create_counter('TEST-INSTRUMENT') + + assert(log_stream.string.match?(/duplicate instrument registration occurred for test-instrument/)) end end + end + + describe '#create_histogram' do + it 'creates and returns an instance of Histogram instrument' do + meter = build_meter + + instrument = meter.create_histogram( + 'test-instrument', + unit: 'ms', + description: 'request duration', + advice: {} + ) - it 'instrument name must not be nil' do - _(-> { meter.create_counter(nil) }).must_raise(INSTRUMENT_NAME_ERROR) + assert(instrument.is_a?(OpenTelemetry::Metrics::Instrument::Histogram)) end - it 'instument name must not be an empty string' do - _(-> { meter.create_counter('') }).must_raise(INSTRUMENT_NAME_ERROR) + it 'logs a warning message when an instrument with the same name already exists' do + meter = build_meter + + OpenTelemetry::TestHelpers.with_test_logger do |log_stream| + meter.create_histogram('test-instrument') + meter.create_histogram('TEST-INSTRUMENT') + + assert(log_stream.string.match?(/duplicate instrument registration occurred for test-instrument/)) + end end + end + + describe '#create_up_down_counter' do + it 'creates and returns an instance of UpDownCounter instrument' do + meter = build_meter + + instrument = meter.create_up_down_counter( + 'test-instrument', + unit: 'items', + description: 'number of items in a queue', + advice: {} + ) - it 'instrument name must have an alphabetic first character' do - _(meter.create_counter('one_counter')) - _(-> { meter.create_counter('1_counter') }).must_raise(INSTRUMENT_NAME_ERROR) + assert(instrument.is_a?(OpenTelemetry::Metrics::Instrument::UpDownCounter)) end - it 'instrument name must not exceed 63 character limit' do - long_name = 'a' * 63 - meter.create_counter(long_name) - _(-> { meter.create_counter(long_name + 'a') }).must_raise(INSTRUMENT_NAME_ERROR) + it 'logs a warning message when an instrument with the same name already exists' do + meter = build_meter + + OpenTelemetry::TestHelpers.with_test_logger do |log_stream| + meter.create_up_down_counter('test-instrument') + meter.create_up_down_counter('TEST-INSTRUMENT') + + assert(log_stream.string.match?(/duplicate instrument registration occurred for test-instrument/)) + end end + end + + describe '#create_observable_counter' do + it 'creates and returns an instance of ObservableCounter instrument' do + meter = build_meter - it 'instrument name must belong to alphanumeric characters, _, ., and -' do - meter.create_counter('a_-..-_a') - _(-> { meter.create_counter('a@') }).must_raise(INSTRUMENT_NAME_ERROR) - _(-> { meter.create_counter('a!') }).must_raise(INSTRUMENT_NAME_ERROR) + instrument = meter.create_observable_counter( + 'test-instrument', + unit: 'fault', + description: 'number of page faults', + callbacks: -> {} + ) + + assert(instrument.is_a?(OpenTelemetry::Metrics::Instrument::ObservableCounter)) end - it 'instrument unit must be ASCII' do - _(-> { meter.create_counter('a_counter', unit: 'á') }).must_raise(INSTRUMENT_UNIT_ERROR) + it 'logs a warning message when an instrument with the same name already exists' do + meter = build_meter + + OpenTelemetry::TestHelpers.with_test_logger do |log_stream| + meter.create_observable_counter('test-instrument') + meter.create_observable_counter('TEST-INSTRUMENT') + + assert(log_stream.string.match?(/duplicate instrument registration occurred for test-instrument/)) + end end + end - it 'instrument unit must not exceed 63 characters' do - long_unit = 'a' * 63 - meter.create_counter('a_counter', unit: long_unit) - _(-> { meter.create_counter('b_counter', unit: long_unit + 'a') }).must_raise(INSTRUMENT_UNIT_ERROR) + describe '#create_observable_gauge' do + it 'creates and returns an instance of ObservableGauge instrument' do + meter = build_meter + + instrument = meter.create_observable_gauge( + 'test-instrument', + unit: 'celsius', + description: 'room temperature', + callbacks: -> {} + ) + + assert(instrument.is_a?(OpenTelemetry::Metrics::Instrument::ObservableGauge)) end - it 'instrument description must be utf8mb3' do - _(-> { meter.create_counter('a_counter', description: '💩'.dup) }).must_raise(INSTRUMENT_DESCRIPTION_ERROR) - _(-> { meter.create_counter('b_counter', description: "\xc2".dup) }).must_raise(INSTRUMENT_DESCRIPTION_ERROR) + it 'logs a warning message when an instrument with the same name already exists' do + meter = build_meter + + OpenTelemetry::TestHelpers.with_test_logger do |log_stream| + meter.create_observable_gauge('test-instrument') + meter.create_observable_gauge('TEST-INSTRUMENT') + + assert(log_stream.string.match?(/duplicate instrument registration occurred for test-instrument/)) + end + end + end + + describe '#create_observable_up_down_counter' do + it 'creates and returns an instance of ObservableUpDownCounter instrument' do + meter = build_meter + + instrument = meter.create_observable_up_down_counter( + 'test-instrument', + unit: 'b', + description: 'process heap size', + callbacks: -> {} + ) + + assert(instrument.is_a?(OpenTelemetry::Metrics::Instrument::ObservableUpDownCounter)) end - it 'instrument description must not exceed 1023 characters' do - long_description = 'a' * 1023 - meter.create_counter('a_counter', description: long_description) - _(-> { meter.create_counter('b_counter', description: long_description + 'a') }).must_raise(INSTRUMENT_DESCRIPTION_ERROR) + it 'logs a warning message when an instrument with the same name already exists' do + meter = build_meter + + OpenTelemetry::TestHelpers.with_test_logger do |log_stream| + meter.create_observable_up_down_counter('test-instrument') + meter.create_observable_up_down_counter('TEST-INSTRUMENT') + + assert(log_stream.string.match?(/duplicate instrument registration occurred for test-instrument/)) + end end end + + def build_meter(name = 'test-meter', **kwargs) + OpenTelemetry::Metrics::Meter.new(name, **kwargs) + end end