opentelemetry-metrics-sdk 0.7.3 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8f7970de692f93abe5d321c766ddfe2d29d85ba06a64a8e4daeac9e50a55bf42
4
- data.tar.gz: 5e147f09a61be36cde37d184a0e2692b949de74c5e0cdd9dae772ed9f40b727a
3
+ metadata.gz: 85882c7aaf7937f0442c130f4d350cf4b94a47e69b90022010ad09a66d5b4e50
4
+ data.tar.gz: 04a4e258be416b2a66bbd27a1caa64bfce75cd411a1fad90ecbc757dc3a86c06
5
5
  SHA512:
6
- metadata.gz: 80cb6c3b9c149bb5d2dfe4d4c3866af45121324490f9b534d63702340ed3ba4eb3bef209e6f559c73a338c11c7e4082344cf49281c3461b06dc55abc168951ee
7
- data.tar.gz: 887d608e30a72332f13a8181c3fbff0444b403d8badefdea8b51797b393d1e5952a37ffcef1d4038bbd17fe6b4d433b9c1e62ae3de6626c17bb13351c3342dc1
6
+ metadata.gz: acd461eca177defb59f52887c77a1e2e4f65b1318da5de8f30c64e2afd7e7358d1316df4c86232e1d8178dc7f88548648e7cc9f25c2d52adadc869edc8f9feb6
7
+ data.tar.gz: a31db4511b453827cc0059884a0fd59b5767ed78db562e3d3b5e048960c0283164658a5054a9f398ef48cac9e245dfd9697da55d1d9155b8de60847f5e798f20
data/CHANGELOG.md CHANGED
@@ -1,24 +1,33 @@
1
1
  # Release History: opentelemetry-metrics-sdk
2
2
 
3
+ ### v0.8.0 / 2025-08-14
4
+
5
+ - BREAKING CHANGE: Update default aggregation temporality for counter, histogram, and up down counter to cumulative
6
+
7
+ - ADDED: Support asynchronous instruments: ObservableGauge, ObservableCounter and ObservableUpDownCounter
8
+ - FIXED: Validate scale range on exponential histograms and raise exception if out of bounds
9
+ - FIXED: Update max instrument name length from 63 to 255 characters and allow `/` in instrument names
10
+ - FIXED: Validate scale range and raise exception if out of bounds for exponential histograms
11
+
3
12
  ### v0.7.3 / 2025-07-09
4
13
 
5
- * FIXED: Stop exporting metrics with empty data points
14
+ - FIXED: Stop exporting metrics with empty data points
6
15
 
7
16
  ### v0.7.2 / 2025-07-03
8
17
 
9
- * FIXED: Coerce aggregation temporality to be a symbol for exponential histograms
18
+ - FIXED: Coerce aggregation temporality to be a symbol for exponential histograms
10
19
 
11
20
  ### v0.7.1 / 2025-05-28
12
21
 
13
- * FIXED: Recover periodic metric readers after forking
22
+ - FIXED: Recover periodic metric readers after forking
14
23
 
15
24
  ### v0.7.0 / 2025-05-13
16
25
 
17
- * ADDED: Add basic exponential histogram
26
+ - ADDED: Add basic exponential histogram
18
27
 
19
28
  ### v0.6.1 / 2025-04-09
20
29
 
21
- * FIXED: Use condition signal to replace sleep and remove timeout.timeout…
30
+ - FIXED: Use condition signal to replace sleep and remove timeout.timeout…
22
31
 
23
32
  ### v0.6.0 / 2025-02-25
24
33
 
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright The OpenTelemetry Authors
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ module OpenTelemetry
8
+ module SDK
9
+ module Metrics
10
+ module Aggregation
11
+ # AggregationTemporality represents the temporality of
12
+ # data point ({NumberDataPoint} and {HistogramDataPoint}) in {Metrics}.
13
+ # It determine whether the data point will be cleared for each metrics pull/export.
14
+ class AggregationTemporality
15
+ class << self
16
+ private :new
17
+
18
+ # Returns a newly created {AggregationTemporality} with temporality == DELTA
19
+ #
20
+ # @return [AggregationTemporality]
21
+ def delta
22
+ new(DELTA)
23
+ end
24
+
25
+ # Returns a newly created {AggregationTemporality} with temporality == CUMULATIVE
26
+ #
27
+ # @return [AggregationTemporality]
28
+ def cumulative
29
+ new(CUMULATIVE)
30
+ end
31
+ end
32
+
33
+ attr_reader :temporality
34
+
35
+ # @api private
36
+ # The constructor is private and only for use internally by the class.
37
+ # Users should use the {delta} and {cumulative} factory methods to obtain
38
+ # a {AggregationTemporality} instance.
39
+ #
40
+ # @param [Integer] temporality One of the status codes below
41
+ def initialize(temporality)
42
+ @temporality = temporality
43
+ end
44
+
45
+ def delta?
46
+ @temporality == :delta
47
+ end
48
+
49
+ def cumulative?
50
+ @temporality == :cumulative
51
+ end
52
+
53
+ # delta: data point will be cleared after each metrics pull/export.
54
+ DELTA = :delta
55
+
56
+ # cumulative: data point will NOT be cleared after metrics pull/export.
57
+ CUMULATIVE = :cumulative
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -10,10 +10,8 @@ module OpenTelemetry
10
10
  module Aggregation
11
11
  # Contains the implementation of the Drop aggregation
12
12
  class Drop
13
- attr_reader :aggregation_temporality
14
-
15
- def initialize(aggregation_temporality: :delta)
16
- @aggregation_temporality = aggregation_temporality
13
+ def initialize
14
+ @aggregation_temporality = nil
17
15
  end
18
16
 
19
17
  def collect(start_time, end_time, data_points)
@@ -30,6 +28,10 @@ module OpenTelemetry
30
28
  )
31
29
  nil
32
30
  end
31
+
32
+ def aggregation_temporality
33
+ nil
34
+ end
33
35
  end
34
36
  end
35
37
  end
@@ -14,24 +14,22 @@ module OpenTelemetry
14
14
  DEFAULT_BOUNDARIES = [0, 5, 10, 25, 50, 75, 100, 250, 500, 1000].freeze
15
15
  private_constant :DEFAULT_BOUNDARIES
16
16
 
17
- attr_reader :aggregation_temporality
18
-
19
17
  # The default value for boundaries represents the following buckets:
20
18
  # (-inf, 0], (0, 5.0], (5.0, 10.0], (10.0, 25.0], (25.0, 50.0],
21
19
  # (50.0, 75.0], (75.0, 100.0], (100.0, 250.0], (250.0, 500.0],
22
20
  # (500.0, 1000.0], (1000.0, +inf)
23
21
  def initialize(
24
- aggregation_temporality: ENV.fetch('OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE', :delta), # TODO: the default should be :cumulative, see issue #1555
22
+ aggregation_temporality: ENV.fetch('OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE', :cumulative),
25
23
  boundaries: DEFAULT_BOUNDARIES,
26
24
  record_min_max: true
27
25
  )
28
- @aggregation_temporality = aggregation_temporality.to_sym
26
+ @aggregation_temporality = aggregation_temporality.to_sym == :delta ? AggregationTemporality.delta : AggregationTemporality.cumulative
29
27
  @boundaries = boundaries && !boundaries.empty? ? boundaries.sort : nil
30
28
  @record_min_max = record_min_max
31
29
  end
32
30
 
33
31
  def collect(start_time, end_time, data_points)
34
- if @aggregation_temporality == :delta
32
+ if @aggregation_temporality.delta?
35
33
  # Set timestamps and 'move' data point values to result.
36
34
  hdps = data_points.values.map! do |hdp|
37
35
  hdp.start_time_unix_nano = start_time
@@ -87,6 +85,10 @@ module OpenTelemetry
87
85
  nil
88
86
  end
89
87
 
88
+ def aggregation_temporality
89
+ @aggregation_temporality.temporality
90
+ end
91
+
90
92
  private
91
93
 
92
94
  def empty_bucket_counts
@@ -19,15 +19,18 @@ module OpenTelemetry
19
19
  attr_reader :aggregation_temporality
20
20
 
21
21
  # relate to min max scale: https://opentelemetry.io/docs/specs/otel/metrics/sdk/#support-a-minimum-and-maximum-scale
22
+ DEFAULT_SIZE = 160
23
+ DEFAULT_SCALE = 20
22
24
  MAX_SCALE = 20
23
25
  MIN_SCALE = -10
24
- MAX_SIZE = 160
26
+ MIN_MAX_SIZE = 2
27
+ MAX_MAX_SIZE = 16_384
25
28
 
26
29
  # The default boundaries are calculated based on default max_size and max_scale values
27
30
  def initialize(
28
31
  aggregation_temporality: ENV.fetch('OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE', :delta),
29
- max_size: MAX_SIZE,
30
- max_scale: MAX_SCALE,
32
+ max_size: DEFAULT_SIZE,
33
+ max_scale: DEFAULT_SCALE,
31
34
  record_min_max: true,
32
35
  zero_threshold: 0
33
36
  )
@@ -175,6 +178,7 @@ module OpenTelemetry
175
178
  end
176
179
 
177
180
  def new_mapping(scale)
181
+ scale = validate_scale(scale)
178
182
  scale <= 0 ? ExponentialHistogram::ExponentMapping.new(scale) : ExponentialHistogram::LogarithmMapping.new(scale)
179
183
  end
180
184
 
@@ -203,17 +207,17 @@ module OpenTelemetry
203
207
  end
204
208
 
205
209
  def validate_scale(scale)
206
- return scale unless scale > MAX_SCALE || scale < MIN_SCALE
210
+ raise ArgumentError, "Scale #{scale} is larger than maximum scale #{MAX_SCALE}" if scale > MAX_SCALE
211
+ raise ArgumentError, "Scale #{scale} is smaller than minimum scale #{MIN_SCALE}" if scale < MIN_SCALE
207
212
 
208
- OpenTelemetry.logger.warn "Scale #{scale} is invalid, using default max scale #{MAX_SCALE}"
209
- MAX_SCALE
213
+ scale
210
214
  end
211
215
 
212
216
  def validate_size(size)
213
- return size unless size > MAX_SIZE || size < 0
217
+ raise ArgumentError, "Max size #{size} is smaller than minimum size #{MIN_MAX_SIZE}" if size < MIN_MAX_SIZE
218
+ raise ArgumentError, "Max size #{size} is larger than maximum size #{MAX_MAX_SIZE}" if size > MAX_MAX_SIZE
214
219
 
215
- OpenTelemetry.logger.warn "Size #{size} is invalid, using default max size #{MAX_SIZE}"
216
- MAX_SIZE
220
+ size
217
221
  end
218
222
  end
219
223
  end
@@ -10,14 +10,12 @@ module OpenTelemetry
10
10
  module Aggregation
11
11
  # Contains the implementation of the LastValue aggregation
12
12
  class LastValue
13
- attr_reader :aggregation_temporality
14
-
15
13
  def initialize(aggregation_temporality: :delta)
16
- @aggregation_temporality = aggregation_temporality
14
+ @aggregation_temporality = aggregation_temporality == :cumulative ? AggregationTemporality.cumulative : AggregationTemporality.delta
17
15
  end
18
16
 
19
17
  def collect(start_time, end_time, data_points)
20
- if @aggregation_temporality == :delta
18
+ if @aggregation_temporality.delta?
21
19
  # Set timestamps and 'move' data point values to result.
22
20
  ndps = data_points.values.map! do |ndp|
23
21
  ndp.start_time_unix_nano = start_time
@@ -46,6 +44,10 @@ module OpenTelemetry
46
44
  )
47
45
  nil
48
46
  end
47
+
48
+ def aggregation_temporality
49
+ @aggregation_temporality.temporality
50
+ end
49
51
  end
50
52
  end
51
53
  end
@@ -11,16 +11,13 @@ module OpenTelemetry
11
11
  # Contains the implementation of the Sum aggregation
12
12
  # https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#sum-aggregation
13
13
  class Sum
14
- attr_reader :aggregation_temporality
15
-
16
- def initialize(aggregation_temporality: ENV.fetch('OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE', :delta), monotonic: false)
17
- # TODO: the default should be :cumulative, see issue #1555
18
- @aggregation_temporality = aggregation_temporality.to_sym
14
+ def initialize(aggregation_temporality: ENV.fetch('OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE', :cumulative), monotonic: false)
15
+ @aggregation_temporality = aggregation_temporality.to_sym == :delta ? AggregationTemporality.delta : AggregationTemporality.cumulative
19
16
  @monotonic = monotonic
20
17
  end
21
18
 
22
19
  def collect(start_time, end_time, data_points)
23
- if @aggregation_temporality == :delta
20
+ if @aggregation_temporality.delta?
24
21
  # Set timestamps and 'move' data point values to result.
25
22
  ndps = data_points.values.map! do |ndp|
26
23
  ndp.start_time_unix_nano = start_time
@@ -57,6 +54,10 @@ module OpenTelemetry
57
54
  ndp.value += increment
58
55
  nil
59
56
  end
57
+
58
+ def aggregation_temporality
59
+ @aggregation_temporality.temporality
60
+ end
60
61
  end
61
62
  end
62
63
  end
@@ -15,6 +15,7 @@ module OpenTelemetry
15
15
  end
16
16
  end
17
17
 
18
+ require 'opentelemetry/sdk/metrics/aggregation/aggregation_temporality'
18
19
  require 'opentelemetry/sdk/metrics/aggregation/number_data_point'
19
20
  require 'opentelemetry/sdk/metrics/aggregation/histogram_data_point'
20
21
  require 'opentelemetry/sdk/metrics/aggregation/explicit_bucket_histogram'
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright The OpenTelemetry Authors
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ module OpenTelemetry
8
+ module SDK
9
+ module Metrics
10
+ module Instrument
11
+ # {AsynchronousInstrument} contains the common functionality shared across
12
+ # the asynchronous instruments SDK instruments.
13
+ class AsynchronousInstrument
14
+ def initialize(name, unit, description, callback, instrumentation_scope, meter_provider)
15
+ @name = name
16
+ @unit = unit
17
+ @description = description
18
+ @instrumentation_scope = instrumentation_scope
19
+ @meter_provider = meter_provider
20
+ @metric_streams = []
21
+ @callbacks = []
22
+ @timeout = nil
23
+ @attributes = {}
24
+
25
+ init_callback(callback)
26
+ meter_provider.register_asynchronous_instrument(self)
27
+ end
28
+
29
+ # @api private
30
+ def register_with_new_metric_store(metric_store, aggregation: default_aggregation)
31
+ ms = OpenTelemetry::SDK::Metrics::State::AsynchronousMetricStream.new(
32
+ @name,
33
+ @description,
34
+ @unit,
35
+ instrument_kind,
36
+ @meter_provider,
37
+ @instrumentation_scope,
38
+ aggregation,
39
+ @callbacks,
40
+ @timeout,
41
+ @attributes
42
+ )
43
+ @metric_streams << ms
44
+ metric_store.add_metric_stream(ms)
45
+ end
46
+
47
+ # The API MUST support creation of asynchronous instruments by passing zero or more callback functions
48
+ # to be permanently registered to the newly created instrument.
49
+ def init_callback(callback)
50
+ if callback.instance_of?(Proc)
51
+ @callbacks << callback
52
+ elsif callback.instance_of?(Array)
53
+ callback.each { |cb| @callbacks << cb if cb.instance_of?(Proc) }
54
+ else
55
+ OpenTelemetry.logger.warn "Only accept single Proc or Array of Proc for initialization with callback (given callback #{callback.class}"
56
+ end
57
+ end
58
+
59
+ # Where the API supports registration of callback functions after asynchronous instrumentation creation,
60
+ # the user MUST be able to undo registration of the specific callback after its registration by some means.
61
+ def register_callback(callback)
62
+ if callback.instance_of?(Proc)
63
+ @callbacks << callback
64
+ callback
65
+ else
66
+ OpenTelemetry.logger.warn "Only accept single Proc for registering callback (given callback #{callback.class}"
67
+ end
68
+ end
69
+
70
+ def unregister(callback)
71
+ @callbacks.delete(callback)
72
+ end
73
+
74
+ def timeout(timeout)
75
+ @timeout = timeout
76
+ end
77
+
78
+ def add_attributes(attributes)
79
+ @attributes.merge!(attributes) if attributes.instance_of?(Hash)
80
+ end
81
+
82
+ private
83
+
84
+ # update the observed value (after calling observe)
85
+ # invoke callback will execute callback and export metric_data that is observed
86
+ def update(timeout, attributes)
87
+ @metric_streams.each { |ms| ms.invoke_callback(timeout, attributes) }
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -8,16 +8,31 @@ module OpenTelemetry
8
8
  module SDK
9
9
  module Metrics
10
10
  module Instrument
11
- # {ObservableCounter} is the SDK implementation of {OpenTelemetry::Metrics::ObservableCounter}.
12
- class ObservableCounter < OpenTelemetry::Metrics::Instrument::ObservableCounter
13
- attr_reader :name, :unit, :description
11
+ # {ObservableCounter} is the SDK implementation of {OpenTelemetry::SDK::Metrics::Instrument::AsynchronousInstrument}.
12
+ # Asynchronous Counter is an asynchronous Instrument which reports monotonically increasing value(s) when the instrument is being observed.
13
+ class ObservableCounter < OpenTelemetry::SDK::Metrics::Instrument::AsynchronousInstrument
14
+ # Returns the instrument kind as a Symbol
15
+ #
16
+ # @return [Symbol]
17
+ def instrument_kind
18
+ :observable_counter
19
+ end
20
+
21
+ # Observe the ObservableCounter with fixed timeout duration.
22
+ #
23
+ # @param [int] timeout The timeout duration for callback to run, which MUST be a non-negative numeric value.
24
+ # @param [Hash{String => String, Numeric, Boolean, Array<String, Numeric, Boolean>}] attributes
25
+ # Values must be non-nil and (array of) string, boolean or numeric type.
26
+ # Array values must not contain nil elements and all elements must be of
27
+ # the same basic type (string, numeric, boolean).
28
+ def observe(timeout: nil, attributes: {})
29
+ update(timeout, attributes)
30
+ end
31
+
32
+ private
14
33
 
15
- def initialize(name, unit, description, callback, meter)
16
- @name = name
17
- @unit = unit
18
- @description = description
19
- @callback = callback
20
- @meter = meter
34
+ def default_aggregation
35
+ OpenTelemetry::SDK::Metrics::Aggregation::Sum.new(monotonic: true)
21
36
  end
22
37
  end
23
38
  end
@@ -8,16 +8,31 @@ module OpenTelemetry
8
8
  module SDK
9
9
  module Metrics
10
10
  module Instrument
11
- # {ObservableGauge} is the SDK implementation of {OpenTelemetry::Metrics::ObservableGauge}.
12
- class ObservableGauge < OpenTelemetry::Metrics::Instrument::ObservableGauge
13
- attr_reader :name, :unit, :description
11
+ # {ObservableGauge} is the SDK implementation of {OpenTelemetry::SDK::Metrics::Instrument::AsynchronousInstrument}.
12
+ # Asynchronous Gauge is an asynchronous Instrument which reports non-additive value(s) (e.g. the room temperature)
13
+ class ObservableGauge < OpenTelemetry::SDK::Metrics::Instrument::AsynchronousInstrument
14
+ # Returns the instrument kind as a Symbol
15
+ #
16
+ # @return [Symbol]
17
+ def instrument_kind
18
+ :observable_gauge
19
+ end
20
+
21
+ # Observe the Gauge with fixed timeout duration.
22
+ #
23
+ # @param [int] timeout The timeout duration for callback to run, which MUST be a non-negative numeric value.
24
+ # @param [Hash{String => String, Numeric, Boolean, Array<String, Numeric, Boolean>}] attributes
25
+ # Values must be non-nil and (array of) string, boolean or numeric type.
26
+ # Array values must not contain nil elements and all elements must be of
27
+ # the same basic type (string, numeric, boolean).
28
+ def observe(timeout: nil, attributes: {})
29
+ update(timeout, attributes)
30
+ end
31
+
32
+ private
14
33
 
15
- def initialize(name, unit, description, callback, meter)
16
- @name = name
17
- @unit = unit
18
- @description = description
19
- @callback = callback
20
- @meter = meter
34
+ def default_aggregation
35
+ OpenTelemetry::SDK::Metrics::Aggregation::LastValue.new
21
36
  end
22
37
  end
23
38
  end
@@ -8,16 +8,31 @@ module OpenTelemetry
8
8
  module SDK
9
9
  module Metrics
10
10
  module Instrument
11
- # {ObservableUpDownCounter} is the SDK implementation of {OpenTelemetry::Metrics::ObservableUpDownCounter}.
12
- class ObservableUpDownCounter < OpenTelemetry::Metrics::Instrument::ObservableUpDownCounter
13
- attr_reader :name, :unit, :description
11
+ # {ObservableUpDownCounter} is the SDK implementation of {OpenTelemetry::SDK::Metrics::Instrument::AsynchronousInstrument}.
12
+ # Asynchronous UpDownCounter is an asynchronous Instrument which reports additive value(s) (e.g. the process heap size)
13
+ class ObservableUpDownCounter < OpenTelemetry::SDK::Metrics::Instrument::AsynchronousInstrument
14
+ # Returns the instrument kind as a Symbol
15
+ #
16
+ # @return [Symbol]
17
+ def instrument_kind
18
+ :observable_up_down_counter
19
+ end
20
+
21
+ # Observe the UpDownCounter with fixed timeout duration.
22
+ #
23
+ # @param [int] timeout The timeout duration for callback to run, which MUST be a non-negative numeric value.
24
+ # @param [Hash{String => String, Numeric, Boolean, Array<String, Numeric, Boolean>}] attributes
25
+ # Values must be non-nil and (array of) string, boolean or numeric type.
26
+ # Array values must not contain nil elements and all elements must be of
27
+ # the same basic type (string, numeric, boolean).
28
+ def observe(timeout: nil, attributes: {})
29
+ update(timeout, attributes)
30
+ end
31
+
32
+ private
14
33
 
15
- def initialize(name, unit, description, callback, meter)
16
- @name = name
17
- @unit = unit
18
- @description = description
19
- @callback = callback
20
- @meter = meter
34
+ def default_aggregation
35
+ OpenTelemetry::SDK::Metrics::Aggregation::Sum.new(aggregation_temporality: :delta, monotonic: false)
21
36
  end
22
37
  end
23
38
  end
@@ -14,6 +14,7 @@ module OpenTelemetry
14
14
  end
15
15
 
16
16
  require 'opentelemetry/sdk/metrics/instrument/synchronous_instrument'
17
+ require 'opentelemetry/sdk/metrics/instrument/asynchronous_instrument'
17
18
  require 'opentelemetry/sdk/metrics/instrument/counter'
18
19
  require 'opentelemetry/sdk/metrics/instrument/histogram'
19
20
  require 'opentelemetry/sdk/metrics/instrument/observable_counter'
@@ -11,7 +11,7 @@ module OpenTelemetry
11
11
  module Metrics
12
12
  # {Meter} is the SDK implementation of {OpenTelemetry::Metrics::Meter}.
13
13
  class Meter < OpenTelemetry::Metrics::Meter
14
- NAME_REGEX = /\A[a-zA-Z][-.\w]{0,62}\z/
14
+ NAME_REGEX = %r{\A[a-zA-Z][-./\w]{0,254}\z}
15
15
 
16
16
  # @api private
17
17
  #
@@ -28,6 +28,30 @@ module OpenTelemetry
28
28
  @meter_provider = meter_provider
29
29
  end
30
30
 
31
+ # Multiple-instrument callbacks
32
+ # Callbacks registered after the time of instrument creation MAY be associated with multiple instruments.
33
+ # Related spec: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/api.md#multiple-instrument-callbacks
34
+ # Related spec: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/api.md#synchronous-instrument-api
35
+ #
36
+ # @param [Array] instruments A list (or tuple, etc.) of Instruments used in the callback function.
37
+ # @param [Proc] callback A callback function
38
+ #
39
+ # It is RECOMMENDED that the API authors use one of the following forms for the callback function:
40
+ # The list (or tuple, etc.) returned by the callback function contains (Instrument, Measurement) pairs.
41
+ # the Observable Result parameter receives an additional (Instrument, Measurement) pairs
42
+ # Here it chose the second form
43
+ def register_callback(instruments, callback)
44
+ instruments.each do |instrument|
45
+ instrument.register_callback(callback)
46
+ end
47
+ end
48
+
49
+ def unregister(instruments, callback)
50
+ instruments.each do |instrument|
51
+ instrument.unregister(callback)
52
+ end
53
+ end
54
+
31
55
  # @api private
32
56
  def add_metric_reader(metric_reader)
33
57
  @instrument_registry.each_value do |instrument|
@@ -126,6 +126,7 @@ module OpenTelemetry
126
126
  end
127
127
  end
128
128
  end
129
+ alias register_asynchronous_instrument register_synchronous_instrument
129
130
 
130
131
  # A View provides SDK users with the flexibility to customize the metrics that are output by the SDK.
131
132
  #
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright The OpenTelemetry Authors
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ module OpenTelemetry
8
+ module SDK
9
+ module Metrics
10
+ module State
11
+ # @api private
12
+ #
13
+ # The AsynchronousMetricStream class provides SDK internal functionality that is not a part of the
14
+ # public API. It extends MetricStream to support asynchronous instruments.
15
+ class AsynchronousMetricStream < MetricStream
16
+ def initialize(
17
+ name,
18
+ description,
19
+ unit,
20
+ instrument_kind,
21
+ meter_provider,
22
+ instrumentation_scope,
23
+ aggregation,
24
+ callback,
25
+ timeout,
26
+ attributes
27
+ )
28
+ # Call parent constructor with common parameters
29
+ super(name, description, unit, instrument_kind, meter_provider, instrumentation_scope, aggregation)
30
+
31
+ # Initialize asynchronous-specific attributes
32
+ @callback = callback
33
+ @start_time = now_in_nano
34
+ @timeout = timeout
35
+ @attributes = attributes
36
+ end
37
+
38
+ # When collect, if there are asynchronous SDK Instruments involved, their callback functions will be triggered.
39
+ # Related spec: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#collect
40
+ # invoke_callback will update the data_points in aggregation
41
+ def collect(start_time, end_time)
42
+ invoke_callback(@timeout, @attributes)
43
+
44
+ # Call parent collect method for the core collection logic
45
+ super(start_time, end_time)
46
+ end
47
+
48
+ def invoke_callback(timeout, attributes)
49
+ if @registered_views.empty?
50
+ @mutex.synchronize do
51
+ Timeout.timeout(timeout || 30) do
52
+ @callback.each do |cb|
53
+ value = cb.call
54
+ @default_aggregation.update(value, attributes, @data_points)
55
+ end
56
+ end
57
+ end
58
+ else
59
+ @registered_views.each do |view|
60
+ @mutex.synchronize do
61
+ Timeout.timeout(timeout || 30) do
62
+ @callback.each do |cb|
63
+ value = cb.call
64
+ merged_attributes = attributes || {}
65
+ merged_attributes.merge!(view.attribute_keys)
66
+ view.aggregation.update(value, merged_attributes, @data_points) if view.valid_aggregation?
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+
74
+ def now_in_nano
75
+ (Time.now.to_r * 1_000_000_000).to_i
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -20,3 +20,4 @@ end
20
20
  require 'opentelemetry/sdk/metrics/state/metric_data'
21
21
  require 'opentelemetry/sdk/metrics/state/metric_store'
22
22
  require 'opentelemetry/sdk/metrics/state/metric_stream'
23
+ require 'opentelemetry/sdk/metrics/state/asynchronous_metric_stream'
@@ -8,7 +8,7 @@ module OpenTelemetry
8
8
  module SDK
9
9
  module Metrics
10
10
  # Current OpenTelemetry metrics sdk version
11
- VERSION = '0.7.3'
11
+ VERSION = '0.8.0'
12
12
  end
13
13
  end
14
14
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: opentelemetry-metrics-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.3
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - OpenTelemetry Authors
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-07-09 00:00:00.000000000 Z
11
+ date: 2025-08-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: opentelemetry-api
@@ -206,6 +206,7 @@ files:
206
206
  - lib/opentelemetry-metrics-sdk.rb
207
207
  - lib/opentelemetry/sdk/metrics.rb
208
208
  - lib/opentelemetry/sdk/metrics/aggregation.rb
209
+ - lib/opentelemetry/sdk/metrics/aggregation/aggregation_temporality.rb
209
210
  - lib/opentelemetry/sdk/metrics/aggregation/drop.rb
210
211
  - lib/opentelemetry/sdk/metrics/aggregation/explicit_bucket_histogram.rb
211
212
  - lib/opentelemetry/sdk/metrics/aggregation/exponential_bucket_histogram.rb
@@ -227,6 +228,7 @@ files:
227
228
  - lib/opentelemetry/sdk/metrics/export/periodic_metric_reader.rb
228
229
  - lib/opentelemetry/sdk/metrics/fork_hooks.rb
229
230
  - lib/opentelemetry/sdk/metrics/instrument.rb
231
+ - lib/opentelemetry/sdk/metrics/instrument/asynchronous_instrument.rb
230
232
  - lib/opentelemetry/sdk/metrics/instrument/counter.rb
231
233
  - lib/opentelemetry/sdk/metrics/instrument/gauge.rb
232
234
  - lib/opentelemetry/sdk/metrics/instrument/histogram.rb
@@ -238,6 +240,7 @@ files:
238
240
  - lib/opentelemetry/sdk/metrics/meter.rb
239
241
  - lib/opentelemetry/sdk/metrics/meter_provider.rb
240
242
  - lib/opentelemetry/sdk/metrics/state.rb
243
+ - lib/opentelemetry/sdk/metrics/state/asynchronous_metric_stream.rb
241
244
  - lib/opentelemetry/sdk/metrics/state/metric_data.rb
242
245
  - lib/opentelemetry/sdk/metrics/state/metric_store.rb
243
246
  - lib/opentelemetry/sdk/metrics/state/metric_stream.rb
@@ -248,10 +251,10 @@ homepage: https://github.com/open-telemetry/opentelemetry-ruby
248
251
  licenses:
249
252
  - Apache-2.0
250
253
  metadata:
251
- changelog_uri: https://open-telemetry.github.io/opentelemetry-ruby/opentelemetry-metrics-sdk/v0.7.3/file.CHANGELOG.html
254
+ changelog_uri: https://open-telemetry.github.io/opentelemetry-ruby/opentelemetry-metrics-sdk/v0.8.0/file.CHANGELOG.html
252
255
  source_code_uri: https://github.com/open-telemetry/opentelemetry-ruby/tree/main/metrics_sdk
253
256
  bug_tracker_uri: https://github.com/open-telemetry/opentelemetry-ruby/issues
254
- documentation_uri: https://open-telemetry.github.io/opentelemetry-ruby/opentelemetry-metrics-sdk/v0.7.3
257
+ documentation_uri: https://open-telemetry.github.io/opentelemetry-ruby/opentelemetry-metrics-sdk/v0.8.0
255
258
  post_install_message:
256
259
  rdoc_options: []
257
260
  require_paths: