opentelemetry-metrics-sdk 0.6.0 → 0.7.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: a74df1307dfb40dbdaf08d87781c35d2736916d94ae06eef45b3237b3ba38631
4
- data.tar.gz: 3a108b46150f768890f6cef2acd49557d83331cc4a84613b8c7de38861cfebdb
3
+ metadata.gz: 4ad774f1262340ffa611f512b97fa605e92857ce72262bcdf7c60edc96a4d61e
4
+ data.tar.gz: 5be962ef2df37a6202e3fc99d5efca5846572824568cca61f33beb121daabb7b
5
5
  SHA512:
6
- metadata.gz: 2b168a69a35cee4b798cf9195b0af540d79b2b9cbb687dd98abe295a517e38683fce1976a7028ab822f8e5aec3157b53043ed9ddbb436cf5a2f25101ddc50b61
7
- data.tar.gz: a06ace144467243b00a4d19bec891d66d55f3da2c611d4569e393b805d31979ab47eed39dbb86d4b9d508519b69ce2f827acb3281c446d83fe8201a6e84150fe
6
+ metadata.gz: '09a50b41250b9cbbc69a1d64a19652d9b4bac203f5111b0c9a794d4a15aac0327a67ac846a65cc083ae908dde4c3766d6cb65838b8d1af81b0d33bfac9e41ec4'
7
+ data.tar.gz: dc20ca5183f8423e32dcfb2b7dc810212f19ae2ac59a2026015983381662b388673540df3427a479a81c83756330aac4ce27e95161a50f239d8470fb6c6a4aea
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Release History: opentelemetry-metrics-sdk
2
2
 
3
+ ### v0.7.0 / 2025-05-13
4
+
5
+ * ADDED: Add basic exponential histogram
6
+
7
+ ### v0.6.1 / 2025-04-09
8
+
9
+ * FIXED: Use condition signal to replace sleep and remove timeout.timeout…
10
+
3
11
  ### v0.6.0 / 2025-02-25
4
12
 
5
13
  - ADDED: Support 3.1 Min Version
@@ -0,0 +1,222 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright The OpenTelemetry Authors
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ require_relative 'exponential_histogram/buckets'
8
+ require_relative 'exponential_histogram/log2e_scale_factor'
9
+ require_relative 'exponential_histogram/ieee_754'
10
+ require_relative 'exponential_histogram/logarithm_mapping'
11
+ require_relative 'exponential_histogram/exponent_mapping'
12
+
13
+ module OpenTelemetry
14
+ module SDK
15
+ module Metrics
16
+ module Aggregation
17
+ # Contains the implementation of the {https://opentelemetry.io/docs/specs/otel/metrics/data-model/#exponentialhistogram ExponentialBucketHistogram} aggregation
18
+ class ExponentialBucketHistogram # rubocop:disable Metrics/ClassLength
19
+ attr_reader :aggregation_temporality
20
+
21
+ # relate to min max scale: https://opentelemetry.io/docs/specs/otel/metrics/sdk/#support-a-minimum-and-maximum-scale
22
+ MAX_SCALE = 20
23
+ MIN_SCALE = -10
24
+ MAX_SIZE = 160
25
+
26
+ # The default boundaries are calculated based on default max_size and max_scale values
27
+ def initialize(
28
+ aggregation_temporality: ENV.fetch('OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE', :delta),
29
+ max_size: MAX_SIZE,
30
+ max_scale: MAX_SCALE,
31
+ record_min_max: true,
32
+ zero_threshold: 0
33
+ )
34
+ @aggregation_temporality = aggregation_temporality
35
+ @record_min_max = record_min_max
36
+ @min = Float::INFINITY
37
+ @max = -Float::INFINITY
38
+ @sum = 0
39
+ @count = 0
40
+ @zero_threshold = zero_threshold
41
+ @zero_count = 0
42
+ @size = validate_size(max_size)
43
+ @scale = validate_scale(max_scale)
44
+
45
+ @mapping = new_mapping(@scale)
46
+ end
47
+
48
+ def collect(start_time, end_time, data_points)
49
+ if @aggregation_temporality == :delta
50
+ # Set timestamps and 'move' data point values to result.
51
+ hdps = data_points.values.map! do |hdp|
52
+ hdp.start_time_unix_nano = start_time
53
+ hdp.time_unix_nano = end_time
54
+ hdp
55
+ end
56
+ data_points.clear
57
+ hdps
58
+ else
59
+ # Update timestamps and take a snapshot.
60
+ data_points.values.map! do |hdp|
61
+ hdp.start_time_unix_nano ||= start_time # Start time of a data point is from the first observation.
62
+ hdp.time_unix_nano = end_time
63
+ hdp = hdp.dup
64
+ hdp.positive = hdp.positive.dup
65
+ hdp.negative = hdp.negative.dup
66
+ hdp
67
+ end
68
+ end
69
+ end
70
+
71
+ # rubocop:disable Metrics/MethodLength
72
+ def update(amount, attributes, data_points)
73
+ # fetch or initialize the ExponentialHistogramDataPoint
74
+ hdp = data_points.fetch(attributes) do
75
+ if @record_min_max
76
+ min = Float::INFINITY
77
+ max = -Float::INFINITY
78
+ end
79
+
80
+ data_points[attributes] = ExponentialHistogramDataPoint.new(
81
+ attributes,
82
+ nil, # :start_time_unix_nano
83
+ 0, # :time_unix_nano
84
+ 0, # :count
85
+ 0, # :sum
86
+ @scale, # :scale
87
+ @zero_count, # :zero_count
88
+ ExponentialHistogram::Buckets.new, # :positive
89
+ ExponentialHistogram::Buckets.new, # :negative
90
+ 0, # :flags
91
+ nil, # :exemplars
92
+ min, # :min
93
+ max, # :max
94
+ @zero_threshold # :zero_threshold)
95
+ )
96
+ end
97
+
98
+ # Start to populate the data point (esp. the buckets)
99
+ if @record_min_max
100
+ hdp.max = amount if amount > hdp.max
101
+ hdp.min = amount if amount < hdp.min
102
+ end
103
+
104
+ hdp.sum += amount
105
+ hdp.count += 1
106
+
107
+ if amount.abs <= @zero_threshold
108
+ hdp.zero_count += 1
109
+ hdp.scale = 0 if hdp.count == hdp.zero_count # if always getting zero, then there is no point to keep doing the update
110
+ return
111
+ end
112
+
113
+ # rescale, map to index, update the buckets here
114
+ buckets = amount.positive? ? hdp.positive : hdp.negative
115
+ amount = -amount if amount.negative?
116
+
117
+ bucket_index = @mapping.map_to_index(amount)
118
+
119
+ rescaling_needed = false
120
+ low = high = 0
121
+
122
+ if buckets.counts == [0] # special case of empty
123
+ buckets.index_start = bucket_index
124
+ buckets.index_end = bucket_index
125
+ buckets.index_base = bucket_index
126
+
127
+ elsif bucket_index < buckets.index_start && (buckets.index_end - bucket_index) >= @size
128
+ rescaling_needed = true
129
+ low = bucket_index
130
+ high = buckets.index_end
131
+
132
+ elsif bucket_index > buckets.index_end && (bucket_index - buckets.index_start) >= @size
133
+ rescaling_needed = true
134
+ low = buckets.index_start
135
+ high = bucket_index
136
+ end
137
+
138
+ if rescaling_needed
139
+ scale_change = get_scale_change(low, high)
140
+ downscale(scale_change, hdp.positive, hdp.negative)
141
+ new_scale = @mapping.scale - scale_change
142
+ hdp.scale = new_scale
143
+ @mapping = new_mapping(new_scale)
144
+ bucket_index = @mapping.map_to_index(amount)
145
+
146
+ OpenTelemetry.logger.debug "Rescaled with new scale #{new_scale} from #{low} and #{high}; bucket_index is updated to #{bucket_index}"
147
+ end
148
+
149
+ # adjust buckets based on the bucket_index
150
+ if bucket_index < buckets.index_start
151
+ span = buckets.index_end - bucket_index
152
+ grow_buckets(span, buckets)
153
+ buckets.index_start = bucket_index
154
+ elsif bucket_index > buckets.index_end
155
+ span = bucket_index - buckets.index_start
156
+ grow_buckets(span, buckets)
157
+ buckets.index_end = bucket_index
158
+ end
159
+
160
+ bucket_index -= buckets.index_base
161
+ bucket_index += buckets.counts.size if bucket_index.negative?
162
+
163
+ buckets.increment_bucket(bucket_index)
164
+ nil
165
+ end
166
+ # rubocop:enable Metrics/MethodLength
167
+
168
+ private
169
+
170
+ def grow_buckets(span, buckets)
171
+ return if span < buckets.counts.size
172
+
173
+ OpenTelemetry.logger.debug "buckets need to grow to #{span + 1} from #{buckets.counts.size} (max bucket size #{@size})"
174
+ buckets.grow(span + 1, @size)
175
+ end
176
+
177
+ def new_mapping(scale)
178
+ scale <= 0 ? ExponentialHistogram::ExponentMapping.new(scale) : ExponentialHistogram::LogarithmMapping.new(scale)
179
+ end
180
+
181
+ def empty_counts
182
+ @boundaries ? Array.new(@boundaries.size + 1, 0) : nil
183
+ end
184
+
185
+ def get_scale_change(low, high)
186
+ # puts "get_scale_change: low: #{low}, high: #{high}, @size: #{@size}"
187
+ # python code also produce 18 with 0,1048575, the high is little bit off
188
+ # just checked, the mapping is also ok, produce the 1048575
189
+ change = 0
190
+ while high - low >= @size
191
+ high >>= 1
192
+ low >>= 1
193
+ change += 1
194
+ end
195
+ change
196
+ end
197
+
198
+ def downscale(change, positive, negative)
199
+ return if change <= 0
200
+
201
+ positive.downscale(change)
202
+ negative.downscale(change)
203
+ end
204
+
205
+ def validate_scale(scale)
206
+ return scale unless scale > MAX_SCALE || scale < MIN_SCALE
207
+
208
+ OpenTelemetry.logger.warn "Scale #{scale} is invalid, using default max scale #{MAX_SCALE}"
209
+ MAX_SCALE
210
+ end
211
+
212
+ def validate_size(size)
213
+ return size unless size > MAX_SIZE || size < 0
214
+
215
+ OpenTelemetry.logger.warn "Size #{size} is invalid, using default max size #{MAX_SIZE}"
216
+ MAX_SIZE
217
+ end
218
+ end
219
+ end
220
+ end
221
+ end
222
+ end
@@ -0,0 +1,113 @@
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
+ module ExponentialHistogram
12
+ # Buckets is the fundamental building block of exponential histogram that store bucket/boundary value
13
+ class Buckets
14
+ attr_accessor :index_start, :index_end, :index_base
15
+
16
+ def initialize
17
+ @counts = [0]
18
+ @index_base = 0
19
+ @index_start = 0
20
+ @index_end = 0
21
+ end
22
+
23
+ # grow simply expand the @counts size
24
+ def grow(needed, max_size)
25
+ size = @counts.size
26
+ bias = @index_base - @index_start
27
+ old_positive_limit = size - bias
28
+
29
+ new_size = [2**Math.log2(needed).ceil, max_size].min
30
+
31
+ new_positive_limit = new_size - bias
32
+
33
+ tmp = Array.new(new_size, 0)
34
+ tmp[new_positive_limit..-1] = @counts[old_positive_limit..]
35
+ tmp[0...old_positive_limit] = @counts[0...old_positive_limit]
36
+ @counts = tmp
37
+ end
38
+
39
+ def offset
40
+ @index_start
41
+ end
42
+
43
+ def offset_counts
44
+ bias = @index_base - @index_start
45
+ @counts[-bias..] + @counts[0...-bias]
46
+ end
47
+ alias counts offset_counts
48
+
49
+ def length
50
+ return 0 if @counts.empty?
51
+ return 0 if @index_end == @index_start && counts[0] == 0
52
+
53
+ @index_end - @index_start + 1
54
+ end
55
+
56
+ def get_bucket(key)
57
+ bias = @index_base - @index_start
58
+
59
+ key += @counts.size if key < bias
60
+ key -= bias
61
+
62
+ @counts[key]
63
+ end
64
+
65
+ def downscale(amount)
66
+ bias = @index_base - @index_start
67
+
68
+ if bias != 0
69
+ @index_base = @index_start
70
+ @counts.reverse!
71
+ @counts = @counts[0...bias].reverse + @counts[bias..].reverse
72
+ end
73
+
74
+ size = 1 + @index_end - @index_start
75
+ each = 1 << amount
76
+ inpos = 0
77
+ outpos = 0
78
+ pos = @index_start
79
+
80
+ while pos <= @index_end
81
+ mod = pos % each
82
+ mod += each if mod < 0
83
+
84
+ inds = mod
85
+
86
+ while inds < each && inpos < size
87
+ if outpos != inpos
88
+ @counts[outpos] += @counts[inpos]
89
+ @counts[inpos] = 0
90
+ end
91
+
92
+ inpos += 1
93
+ pos += 1
94
+ inds += 1
95
+ end
96
+
97
+ outpos += 1
98
+ end
99
+
100
+ @index_start >>= amount
101
+ @index_end >>= amount
102
+ @index_base = @index_start
103
+ end
104
+
105
+ def increment_bucket(bucket_index, increment = 1)
106
+ @counts[bucket_index] += increment
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,47 @@
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
+ module ExponentialHistogram
12
+ # LogarithmMapping for mapping when scale < 0
13
+ class ExponentMapping
14
+ attr_reader :scale
15
+
16
+ def initialize(scale)
17
+ @scale = scale
18
+ @min_normal_lower_boundary_index = calculate_min_normal_lower_boundary_index(scale)
19
+ @max_normal_lower_boundary_index = IEEE754::MAX_NORMAL_EXPONENT >> -@scale
20
+ end
21
+
22
+ def map_to_index(value)
23
+ return @min_normal_lower_boundary_index if value < IEEE754::MIN_NORMAL_VALUE
24
+
25
+ exponent = IEEE754.get_ieee_754_exponent(value)
26
+ correction = (IEEE754.get_ieee_754_mantissa(value) - 1) >> IEEE754::MANTISSA_WIDTH
27
+ (exponent + correction) >> -@scale
28
+ end
29
+
30
+ def calculate_min_normal_lower_boundary_index(scale)
31
+ inds = IEEE754::MIN_NORMAL_EXPONENT >> -scale
32
+ inds -= 1 if -scale < 2
33
+ inds
34
+ end
35
+
36
+ # for testing
37
+ def get_lower_boundary(inds)
38
+ raise StandardError, 'mapping underflow' if inds < @min_normal_lower_boundary_index || inds > @max_normal_lower_boundary_index
39
+
40
+ Math.ldexp(1, inds << -@scale)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,42 @@
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
+ module ExponentialHistogram
12
+ # IEEE754 standard for floating-point calculation
13
+ module IEEE754
14
+ MANTISSA_WIDTH = 52
15
+ EXPONENT_WIDTH = 11
16
+
17
+ MANTISSA_MASK = (1 << MANTISSA_WIDTH) - 1
18
+ EXPONENT_BIAS = (2**(EXPONENT_WIDTH - 1)) - 1
19
+ EXPONENT_MASK = ((1 << EXPONENT_WIDTH) - 1) << MANTISSA_WIDTH
20
+ SIGN_MASK = 1 << (EXPONENT_WIDTH + MANTISSA_WIDTH)
21
+
22
+ MIN_NORMAL_EXPONENT = -EXPONENT_BIAS + 1
23
+ MAX_NORMAL_EXPONENT = EXPONENT_BIAS
24
+
25
+ MIN_NORMAL_VALUE = Float::MIN
26
+ MAX_NORMAL_VALUE = Float::MAX
27
+
28
+ def self.get_ieee_754_exponent(value)
29
+ bits = [value].pack('d').unpack1('Q')
30
+ ((bits & EXPONENT_MASK) >> MANTISSA_WIDTH) - EXPONENT_BIAS
31
+ end
32
+
33
+ def self.get_ieee_754_mantissa(value)
34
+ bits = [value].pack('d').unpack1('Q')
35
+ bits & MANTISSA_MASK
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,30 @@
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
+ module ExponentialHistogram
12
+ # Log2eScaleFactor is precomputed scale factor value
13
+ class Log2eScaleFactor
14
+ MAX_SCALE = 20
15
+
16
+ LOG2E_SCALE_BUCKETS = (0..MAX_SCALE).map do |scale|
17
+ log2e = 1 / Math.log(2)
18
+ Math.ldexp(log2e, scale)
19
+ end
20
+
21
+ # for testing
22
+ def self.log2e_scale_buckets
23
+ LOG2E_SCALE_BUCKETS
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,56 @@
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
+ module ExponentialHistogram
12
+ # LogarithmMapping for mapping when scale > 0
13
+ class LogarithmMapping
14
+ attr_reader :scale
15
+
16
+ def initialize(scale)
17
+ @scale = scale
18
+ @scale_factor = Log2eScaleFactor::LOG2E_SCALE_BUCKETS[scale] # scale_factor is used for mapping the index
19
+ @min_normal_lower_boundary_index = IEEE754::MIN_NORMAL_EXPONENT << @scale
20
+ @max_normal_lower_boundary_index = ((IEEE754::MAX_NORMAL_EXPONENT + 1) << @scale) - 1
21
+ end
22
+
23
+ def map_to_index(value)
24
+ return @min_normal_lower_boundary_index - 1 if value <= IEEE754::MIN_NORMAL_VALUE
25
+
26
+ if IEEE754.get_ieee_754_mantissa(value) == 0
27
+ exponent = IEEE754.get_ieee_754_exponent(value)
28
+ return (exponent << @scale) - 1
29
+ end
30
+
31
+ [(Math.log(value) * @scale_factor).floor, @max_normal_lower_boundary_index].min
32
+ end
33
+
34
+ # for testing
35
+ def get_lower_boundary(inds)
36
+ if inds >= @max_normal_lower_boundary_index
37
+ return 2 * Math.exp((inds - (1 << @scale)) / @scale_factor) if inds == @max_normal_lower_boundary_index
38
+
39
+ raise StandardError, 'mapping overflow'
40
+ end
41
+
42
+ if inds <= @min_normal_lower_boundary_index
43
+ return IEEE754::MIN_NORMAL_VALUE if inds == @min_normal_lower_boundary_index
44
+ return Math.exp((inds + (1 << @scale)) / @scale_factor) / 2 if inds == @min_normal_lower_boundary_index - 1
45
+
46
+ raise StandardError, 'mapping underflow'
47
+ end
48
+
49
+ Math.exp(inds / @scale_factor)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,31 @@
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
+ # rubocop:disable Lint/StructNewOverride
12
+
13
+ ExponentialHistogramDataPoint = Struct.new(:attributes, # optional Hash{String => String, Numeric, Boolean, Array<String, Numeric, Boolean>}
14
+ :start_time_unix_nano, # Integer nanoseconds since Epoch
15
+ :time_unix_nano, # Integer nanoseconds since Epoch
16
+ :count, # Integer count is the number of values in the population. Must be non-negative
17
+ :sum, # Integer sum of the values in the population. If count is zero then this field then this field must be zero
18
+ :scale, # Integer scale factor
19
+ :zero_count, # Integer special bucket that count of observations that fall into the zero bucket
20
+ :positive, # Buckets representing the positive range of the histogram.
21
+ :negative, # Buckets representing the negative range of the histogram.
22
+ :flags, # Integer flags associated with the data point.
23
+ :exemplars, # optional List of exemplars collected from measurements that were used to form the data point
24
+ :min, # optional Float min is the minimum value over (start_time, end_time].
25
+ :max, # optional Float max is the maximum value over (start_time, end_time].
26
+ :zero_threshold) # optional Float the threshold for the zero bucket
27
+ # rubocop:enable Lint/StructNewOverride
28
+ end
29
+ end
30
+ end
31
+ end
@@ -21,3 +21,5 @@ require 'opentelemetry/sdk/metrics/aggregation/explicit_bucket_histogram'
21
21
  require 'opentelemetry/sdk/metrics/aggregation/sum'
22
22
  require 'opentelemetry/sdk/metrics/aggregation/last_value'
23
23
  require 'opentelemetry/sdk/metrics/aggregation/drop'
24
+ require 'opentelemetry/sdk/metrics/aggregation/exponential_histogram_data_point'
25
+ require 'opentelemetry/sdk/metrics/aggregation/exponential_bucket_histogram'
@@ -33,14 +33,22 @@ module OpenTelemetry
33
33
  @thread = nil
34
34
  @continue = false
35
35
  @mutex = Mutex.new
36
+ @condition = ConditionVariable.new
36
37
  @export_mutex = Mutex.new
37
38
 
38
39
  start
39
40
  end
40
41
 
42
+ # Shuts the @thread down and set @continue to false; it will block
43
+ # until the shutdown thread is finished.
44
+ #
45
+ # @param [optional Numeric] timeout An optional timeout in seconds.
46
+ # @return [Integer] SUCCESS if no error occurred, FAILURE if a
47
+ # non-specific failure occurred.
41
48
  def shutdown(timeout: nil)
42
49
  thread = lock do
43
50
  @continue = false # force termination in next iteration
51
+ @condition.signal
44
52
  @thread
45
53
  end
46
54
  thread&.join(@export_interval)
@@ -52,15 +60,35 @@ module OpenTelemetry
52
60
  Export::FAILURE
53
61
  end
54
62
 
63
+ # Export all metrics to the configured `Exporter` that have not yet
64
+ # been exported.
65
+ #
66
+ # This method should only be called in cases where it is absolutely
67
+ # necessary, such as when using some FaaS providers that may suspend
68
+ # the process after an invocation, but before the `PeriodicMetricReader` exports
69
+ # the completed metrics.
70
+ #
71
+ # @param [optional Numeric] timeout An optional timeout in seconds.
72
+ # @return [Integer] SUCCESS if no error occurred, FAILURE if a
73
+ # non-specific failure occurred.
55
74
  def force_flush(timeout: nil)
56
- export(timeout: timeout)
75
+ export(timeout:)
57
76
  Export::SUCCESS
58
77
  rescue StandardError
59
78
  Export::FAILURE
60
79
  end
61
80
 
81
+ # Check both @thread and @continue object to determine if current
82
+ # PeriodicMetricReader is still alive. If one of them is true/alive,
83
+ # then PeriodicMetricReader is determined as alive
84
+ def alive?
85
+ @continue || @thread.alive?
86
+ end
87
+
62
88
  private
63
89
 
90
+ # Start a thread that continously export metrics within fixed duration.
91
+ # The wait mechanism is using to check @mutex lock with conditional variable
64
92
  def start
65
93
  @continue = true
66
94
  if @exporter.nil?
@@ -70,19 +98,21 @@ module OpenTelemetry
70
98
  else
71
99
  @thread = Thread.new do
72
100
  while @continue
73
- sleep(@export_interval)
74
- begin
75
- Timeout.timeout(@export_timeout) do
76
- export(timeout: @export_timeout)
77
- end
78
- rescue Timeout::Error => e
79
- OpenTelemetry.handle_error(exception: e, message: 'PeriodicMetricReader timeout.')
101
+ lock do
102
+ @condition.wait(@mutex, @export_interval)
103
+ export(timeout: @export_timeout)
80
104
  end
81
105
  end
82
106
  end
83
107
  end
84
108
  end
85
109
 
110
+ # Helper function for the defined exporter to export metrics.
111
+ # It only exports if the collected metrics are not an empty array (collect returns an Array).
112
+ #
113
+ # @param [optional Numeric] timeout An optional timeout in seconds.
114
+ # @return [Integer] SUCCESS if no error occurred, FAILURE if a
115
+ # non-specific failure occurred
86
116
  def export(timeout: nil)
87
117
  @export_mutex.synchronize do
88
118
  collected_metrics = collect
@@ -91,12 +91,12 @@ module OpenTelemetry
91
91
  end
92
92
 
93
93
  def to_s
94
- instrument_info = String.new
94
+ instrument_info = +''
95
95
  instrument_info << "name=#{@name}"
96
96
  instrument_info << " description=#{@description}" if @description
97
97
  instrument_info << " unit=#{@unit}" if @unit
98
98
  @data_points.map do |attributes, value|
99
- metric_stream_string = String.new
99
+ metric_stream_string = +''
100
100
  metric_stream_string << instrument_info
101
101
  metric_stream_string << " attributes=#{attributes}" if attributes
102
102
  metric_stream_string << " #{value}"
@@ -8,7 +8,7 @@ module OpenTelemetry
8
8
  module SDK
9
9
  module Metrics
10
10
  # Current OpenTelemetry metrics sdk version
11
- VERSION = '0.6.0'
11
+ VERSION = '0.7.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.6.0
4
+ version: 0.7.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-02-25 00:00:00.000000000 Z
11
+ date: 2025-05-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: opentelemetry-api
@@ -208,6 +208,13 @@ files:
208
208
  - lib/opentelemetry/sdk/metrics/aggregation.rb
209
209
  - lib/opentelemetry/sdk/metrics/aggregation/drop.rb
210
210
  - lib/opentelemetry/sdk/metrics/aggregation/explicit_bucket_histogram.rb
211
+ - lib/opentelemetry/sdk/metrics/aggregation/exponential_bucket_histogram.rb
212
+ - lib/opentelemetry/sdk/metrics/aggregation/exponential_histogram/buckets.rb
213
+ - lib/opentelemetry/sdk/metrics/aggregation/exponential_histogram/exponent_mapping.rb
214
+ - lib/opentelemetry/sdk/metrics/aggregation/exponential_histogram/ieee_754.rb
215
+ - lib/opentelemetry/sdk/metrics/aggregation/exponential_histogram/log2e_scale_factor.rb
216
+ - lib/opentelemetry/sdk/metrics/aggregation/exponential_histogram/logarithm_mapping.rb
217
+ - lib/opentelemetry/sdk/metrics/aggregation/exponential_histogram_data_point.rb
211
218
  - lib/opentelemetry/sdk/metrics/aggregation/histogram_data_point.rb
212
219
  - lib/opentelemetry/sdk/metrics/aggregation/last_value.rb
213
220
  - lib/opentelemetry/sdk/metrics/aggregation/number_data_point.rb
@@ -240,10 +247,10 @@ homepage: https://github.com/open-telemetry/opentelemetry-ruby
240
247
  licenses:
241
248
  - Apache-2.0
242
249
  metadata:
243
- changelog_uri: https://open-telemetry.github.io/opentelemetry-ruby/opentelemetry-metrics-sdk/v0.6.0/file.CHANGELOG.html
250
+ changelog_uri: https://open-telemetry.github.io/opentelemetry-ruby/opentelemetry-metrics-sdk/v0.7.0/file.CHANGELOG.html
244
251
  source_code_uri: https://github.com/open-telemetry/opentelemetry-ruby/tree/main/metrics_sdk
245
252
  bug_tracker_uri: https://github.com/open-telemetry/opentelemetry-ruby/issues
246
- documentation_uri: https://open-telemetry.github.io/opentelemetry-ruby/opentelemetry-metrics-sdk/v0.6.0
253
+ documentation_uri: https://open-telemetry.github.io/opentelemetry-ruby/opentelemetry-metrics-sdk/v0.7.0
247
254
  post_install_message:
248
255
  rdoc_options: []
249
256
  require_paths: