opentelemetry-metrics-sdk 0.14.0 → 0.15.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: 35222d1da9eadba8252b7110b20a504a5e5b339c7ecc50403b3d6056f6b2c282
4
- data.tar.gz: 23732ae05bea5b79aa6678f636e429d5958a35b96cf7dbd54846212db33d6727
3
+ metadata.gz: fee8234836c749267c8ef70c3a6e1edd1e7f07f2f684be31be5bbb692783bbe5
4
+ data.tar.gz: d95498e59164701c62e415db1d99d20f201f77a7a9d621667603bdb7485c668c
5
5
  SHA512:
6
- metadata.gz: 81abb739ad7ee5191f98d3577a2a7f65323dbb567ba137def5a0f35cf3937caf61f442e6ef3b8320d5cc2c0e6083c02a1f32e58d955d09461df1742f5df0e967
7
- data.tar.gz: 83031636bdddd8317ca29fc8cf21f7bd17ca2f37cc06e16faba61b82d5286ea8e1c218de32461beb647ce8fd6a49928a0f73f26f26e0e219f19276214a744a5b
6
+ metadata.gz: 13fc13a8866034bbd502032c7b6aef1fe485591d065cd01d533eec77ed9aa3fc8c3e11def8317b891bda9347317558ea0322bc211c00d46c3ee0cbc3a57c2bce
7
+ data.tar.gz: 123a2e82d19c865243d45d0e2e29dce12e2c3166813ccdc8ad5e10ea45fb6e31d6d9c54b9558f36c380e8ece670146c9ba1b2450286b03a6ab6c70ec28db0839
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Release History: opentelemetry-metrics-sdk
2
2
 
3
+ ### v0.15.0 / 2026-06-16
4
+
5
+ * BREAKING CHANGE: Metrics cardinality limit (#1909)
6
+ * ADDED: Metrics cardinality limit (#1909)
7
+
3
8
  ### v0.14.0 / 2026-05-12
4
9
 
5
10
  * BREAKING CHANGE: Use trace_based exemplar filter by default (#2112)
@@ -22,7 +22,7 @@ module OpenTelemetry
22
22
  data_points.values.map!(&:dup)
23
23
  end
24
24
 
25
- def update(increment, attributes, data_points, exemplar_offer: false)
25
+ def update(increment, attributes, data_points, cardinality_limit, exemplar_offer: false)
26
26
  data_points[attributes] = NumberDataPoint.new(
27
27
  {},
28
28
  0,
@@ -11,6 +11,7 @@ module OpenTelemetry
11
11
  # Contains the implementation of the ExplicitBucketHistogram aggregation
12
12
  # https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#explicit-bucket-histogram-aggregation
13
13
  class ExplicitBucketHistogram
14
+ OVERFLOW_ATTRIBUTE_SET = { 'otel.metric.overflow' => true }.freeze
14
15
  attr_reader :exemplar_reservoir
15
16
 
16
17
  DEFAULT_BOUNDARIES = [0, 5, 10, 25, 50, 75, 100, 250, 500, 1000].freeze
@@ -59,41 +60,48 @@ module OpenTelemetry
59
60
  end
60
61
  end
61
62
 
62
- def update(amount, attributes, data_points, exemplar_offer: false)
63
- hdp = data_points.fetch(attributes) do
64
- if @record_min_max
65
- min = Float::INFINITY
66
- max = -Float::INFINITY
67
- end
63
+ def update(amount, attributes, data_points, cardinality_limit, exemplar_offer: false)
64
+ hdp = if data_points.key?(attributes)
65
+ data_points[attributes]
66
+ elsif data_points.size >= cardinality_limit - 1
67
+ data_points[OVERFLOW_ATTRIBUTE_SET] || create_new_data_point(OVERFLOW_ATTRIBUTE_SET, data_points)
68
+ else
69
+ create_new_data_point(attributes, data_points)
70
+ end
68
71
 
69
- data_points[attributes] = HistogramDataPoint.new(
70
- attributes,
71
- nil, # :start_time_unix_nano
72
- nil, # :time_unix_nano
73
- 0, # :count
74
- 0, # :sum
75
- empty_bucket_counts, # :bucket_counts
76
- @boundaries, # :explicit_bounds
77
- nil, # :exemplars
78
- min, # :min
79
- max # :max
80
- )
81
- end
72
+ update_histogram_data_point(hdp, amount, exemplar_offer: exemplar_offer)
73
+ nil
74
+ end
82
75
 
83
- reservoir = @exemplar_reservoir_storage[attributes]
84
- unless reservoir
85
- reservoir = @exemplar_reservoir.dup
86
- reservoir.reset
87
- @exemplar_reservoir_storage[attributes] = reservoir
88
- end
76
+ def aggregation_temporality
77
+ @aggregation_temporality.temporality
78
+ end
89
79
 
90
- if exemplar_offer
91
- reservoir.offer(value: amount,
92
- timestamp: OpenTelemetry::Common::Utilities.time_in_nanoseconds,
93
- attributes: attributes,
94
- context: OpenTelemetry::Context.current)
80
+ private
81
+
82
+ def create_new_data_point(attributes, data_points)
83
+ if @record_min_max
84
+ min = Float::INFINITY
85
+ max = -Float::INFINITY
95
86
  end
96
87
 
88
+ data_points[attributes] = HistogramDataPoint.new(
89
+ attributes,
90
+ nil, # :start_time_unix_nano
91
+ nil, # :time_unix_nano
92
+ 0, # :count
93
+ 0, # :sum
94
+ empty_bucket_counts, # :bucket_counts
95
+ @boundaries, # :explicit_bounds
96
+ nil, # :exemplars
97
+ min, # :min
98
+ max # :max
99
+ )
100
+ end
101
+
102
+ def update_histogram_data_point(hdp, amount, exemplar_offer: false)
103
+ reservior_update(hdp.attributes, amount, exemplar_offer)
104
+
97
105
  if @record_min_max
98
106
  hdp.max = amount if amount > hdp.max
99
107
  hdp.min = amount if amount < hdp.min
@@ -101,18 +109,27 @@ module OpenTelemetry
101
109
 
102
110
  hdp.sum += amount
103
111
  hdp.count += 1
104
- if @boundaries
105
- bucket_index = @boundaries.bsearch_index { |i| i >= amount } || @boundaries.size
106
- hdp.bucket_counts[bucket_index] += 1
107
- end
108
- nil
109
- end
112
+ return unless @boundaries
110
113
 
111
- def aggregation_temporality
112
- @aggregation_temporality.temporality
114
+ bucket_index = @boundaries.bsearch_index { |i| i >= amount } || @boundaries.size
115
+ hdp.bucket_counts[bucket_index] += 1
113
116
  end
114
117
 
115
- private
118
+ def reservior_update(attributes, amount, exemplar_offer)
119
+ reservoir = @exemplar_reservoir_storage[attributes]
120
+ unless reservoir
121
+ reservoir = @exemplar_reservoir.dup
122
+ reservoir.reset
123
+ @exemplar_reservoir_storage[attributes] = reservoir
124
+ end
125
+
126
+ return unless exemplar_offer
127
+
128
+ reservoir.offer(value: amount,
129
+ timestamp: OpenTelemetry::Common::Utilities.time_in_nanoseconds,
130
+ attributes: attributes,
131
+ context: OpenTelemetry::Context.current)
132
+ end
116
133
 
117
134
  def empty_bucket_counts
118
135
  @boundaries ? Array.new(@boundaries.size + 1, 0) : nil
@@ -17,6 +17,8 @@ module OpenTelemetry
17
17
  module Aggregation
18
18
  # Contains the implementation of the {https://opentelemetry.io/docs/specs/otel/metrics/data-model/#exponentialhistogram ExponentialBucketHistogram} aggregation
19
19
  class ExponentialBucketHistogram # rubocop:disable Metrics/ClassLength
20
+ OVERFLOW_ATTRIBUTE_SET = { 'otel.metric.overflow' => true }.freeze
21
+
20
22
  # relate to min max scale: https://opentelemetry.io/docs/specs/otel/metrics/sdk/#support-a-minimum-and-maximum-scale
21
23
  DEFAULT_SIZE = 160
22
24
  DEFAULT_SCALE = 20
@@ -94,7 +96,6 @@ module OpenTelemetry
94
96
 
95
97
  # this will slow down the operation especially if large amount of data_points present
96
98
  # but it should be fine since with cumulative, the data_points are merged into previous_* and not kept in data_points
97
- # rubocop:disable Metrics/BlockLength
98
99
  data_points.each do |attributes, hdp|
99
100
  # Store current values
100
101
  current_positive = hdp.positive
@@ -181,7 +182,6 @@ module OpenTelemetry
181
182
  merged_data_points[attributes] = merged_hdp
182
183
  @previous_mappings[attributes] = @mappings[attributes] if @mappings[attributes] # Preserve mapping for next collection
183
184
  end
184
- # rubocop:enable Metrics/BlockLength
185
185
 
186
186
  # when you have no local_data_points, the loop from cumulative aggregation will not run
187
187
  # so return last merged data points if exists
@@ -221,49 +221,53 @@ module OpenTelemetry
221
221
  # rubocop:enable Metrics/MethodLength
222
222
 
223
223
  # this is aggregate in python; there is no merge in aggregate; but rescale happened
224
- # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
225
- def update(amount, attributes, data_points, exemplar_offer: false)
226
- # fetch or initialize the ExponentialHistogramDataPoint
227
- hdp = data_points.fetch(attributes) do
228
- if @record_min_max
229
- min = Float::INFINITY
230
- max = -Float::INFINITY
231
- end
224
+ def update(amount, attributes, data_points, cardinality_limit, exemplar_offer: false)
225
+ hdp = if data_points.key?(attributes)
226
+ data_points[attributes]
227
+ elsif data_points.size >= cardinality_limit - 1
228
+ data_points[OVERFLOW_ATTRIBUTE_SET] || create_new_data_point(OVERFLOW_ATTRIBUTE_SET, data_points)
229
+ else
230
+ create_new_data_point(attributes, data_points)
231
+ end
232
+
233
+ update_histogram_data_point(hdp, attributes, amount, exemplar_offer: exemplar_offer)
234
+ nil
235
+ end
232
236
 
233
- # this code block will only be executed if no data_points was found with the attributes
234
- data_points[attributes] = ExponentialHistogramDataPoint.new(
235
- attributes,
236
- nil, # :start_time_unix_nano
237
- 0, # :time_unix_nano
238
- 0, # :count
239
- 0, # :sum
240
- @scale, # :scale
241
- @zero_count, # :zero_count
242
- ExponentialHistogram::Buckets.new, # :positive
243
- ExponentialHistogram::Buckets.new, # :negative
244
- 0, # :flags
245
- nil, # :exemplars
246
- min, # :min
247
- max, # :max
248
- @zero_threshold # :zero_threshold
249
- )
250
- end
237
+ def aggregation_temporality
238
+ @aggregation_temporality.temporality
239
+ end
251
240
 
252
- reservoir = @exemplar_reservoir_storage[attributes]
253
- unless reservoir
254
- reservoir = @exemplar_reservoir.dup
255
- reservoir.reset
256
- @exemplar_reservoir_storage[attributes] = reservoir
257
- end
241
+ private
258
242
 
259
- if exemplar_offer
260
- reservoir.offer(value: amount,
261
- timestamp: OpenTelemetry::Common::Utilities.time_in_nanoseconds,
262
- attributes: attributes,
263
- context: OpenTelemetry::Context.current)
243
+ def create_new_data_point(attributes, data_points)
244
+ if @record_min_max
245
+ min = Float::INFINITY
246
+ max = -Float::INFINITY
264
247
  end
265
248
 
266
- # Start to populate the data point (esp. the buckets)
249
+ data_points[attributes] = ExponentialHistogramDataPoint.new(
250
+ attributes,
251
+ nil, # :start_time_unix_nano
252
+ 0, # :time_unix_nano
253
+ 0, # :count
254
+ 0, # :sum
255
+ @scale, # :scale
256
+ @zero_count, # :zero_count
257
+ ExponentialHistogram::Buckets.new, # :positive
258
+ ExponentialHistogram::Buckets.new, # :negative
259
+ 0, # :flags
260
+ nil, # :exemplars
261
+ min, # :min
262
+ max, # :max
263
+ @zero_threshold # :zero_threshold
264
+ )
265
+ end
266
+
267
+ # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength
268
+ def update_histogram_data_point(hdp, attributes, amount, exemplar_offer: false)
269
+ reservior_update(attributes, amount, exemplar_offer)
270
+
267
271
  if @record_min_max
268
272
  hdp.max = amount if amount > hdp.max
269
273
  hdp.min = amount if amount < hdp.min
@@ -339,15 +343,8 @@ module OpenTelemetry
339
343
  bucket_index += buckets.counts.size if bucket_index.negative?
340
344
 
341
345
  buckets.increment_bucket(bucket_index)
342
- nil
343
346
  end
344
- # rubocop:enable Metrics/MethodLength, Metrics/CyclomaticComplexity
345
-
346
- def aggregation_temporality
347
- @aggregation_temporality.temporality
348
- end
349
-
350
- private
347
+ # rubocop:enable Metrics/CyclomaticComplexity,Metrics/MethodLength
351
348
 
352
349
  def grow_buckets(span, buckets)
353
350
  return if span < buckets.counts.size
@@ -356,6 +353,22 @@ module OpenTelemetry
356
353
  buckets.grow(span + 1, @size)
357
354
  end
358
355
 
356
+ def reservior_update(attributes, amount, exemplar_offer)
357
+ reservoir = @exemplar_reservoir_storage[attributes]
358
+ unless reservoir
359
+ reservoir = @exemplar_reservoir.dup
360
+ reservoir.reset
361
+ @exemplar_reservoir_storage[attributes] = reservoir
362
+ end
363
+
364
+ return unless exemplar_offer
365
+
366
+ reservoir.offer(value: amount,
367
+ timestamp: OpenTelemetry::Common::Utilities.time_in_nanoseconds,
368
+ attributes: attributes,
369
+ context: OpenTelemetry::Context.current)
370
+ end
371
+
359
372
  def new_mapping(scale)
360
373
  scale = validate_scale(scale)
361
374
  scale <= 0 ? ExponentialHistogram::ExponentMapping.new(scale) : ExponentialHistogram::LogarithmMapping.new(scale)
@@ -10,6 +10,7 @@ module OpenTelemetry
10
10
  module Aggregation
11
11
  # Contains the implementation of the LastValue aggregation
12
12
  class LastValue
13
+ OVERFLOW_ATTRIBUTE_SET = { 'otel.metric.overflow' => true }.freeze
13
14
  attr_reader :exemplar_reservoir
14
15
 
15
16
  # if no reservoir pass from instrument, then use this empty reservoir to avoid no method found error
@@ -33,29 +34,51 @@ module OpenTelemetry
33
34
  ndps
34
35
  end
35
36
 
36
- def update(increment, attributes, data_points, exemplar_offer: false)
37
- reservoir = @exemplar_reservoir_storage[attributes]
38
- unless reservoir
39
- reservoir = @exemplar_reservoir.dup
40
- reservoir.reset
41
- @exemplar_reservoir_storage[attributes] = reservoir
42
- end
37
+ def update(increment, attributes, data_points, cardinality_limit, exemplar_offer: false)
38
+ # Check if we already have this attribute set
39
+ ndp = if data_points.key?(attributes)
40
+ data_points[attributes]
41
+ elsif data_points.size >= cardinality_limit - 1
42
+ data_points[OVERFLOW_ATTRIBUTE_SET] || create_new_data_point(OVERFLOW_ATTRIBUTE_SET, data_points)
43
+ else
44
+ create_new_data_point(attributes, data_points)
45
+ end
43
46
 
44
- if exemplar_offer
45
- reservoir.offer(value: increment,
46
- timestamp: OpenTelemetry::Common::Utilities.time_in_nanoseconds,
47
- attributes: attributes,
48
- context: OpenTelemetry::Context.current)
49
- end
47
+ update_number_data_point(ndp, increment, exemplar_offer: exemplar_offer)
48
+ nil
49
+ end
50
50
 
51
+ private
52
+
53
+ def create_new_data_point(attributes, data_points)
51
54
  data_points[attributes] = NumberDataPoint.new(
52
55
  attributes,
53
56
  nil,
54
57
  nil,
55
- increment,
58
+ 0,
56
59
  nil
57
60
  )
58
- nil
61
+ end
62
+
63
+ def update_number_data_point(ndp, increment, exemplar_offer: false)
64
+ ndp.value = increment
65
+ reservior_update(ndp.attributes, increment, exemplar_offer)
66
+ end
67
+
68
+ def reservior_update(attributes, increment, exemplar_offer)
69
+ reservoir = @exemplar_reservoir_storage[attributes]
70
+ unless reservoir
71
+ reservoir = @exemplar_reservoir.dup
72
+ reservoir.reset
73
+ @exemplar_reservoir_storage[attributes] = reservoir
74
+ end
75
+
76
+ return unless exemplar_offer
77
+
78
+ reservoir.offer(value: increment,
79
+ timestamp: OpenTelemetry::Common::Utilities.time_in_nanoseconds,
80
+ attributes: attributes,
81
+ context: OpenTelemetry::Context.current)
59
82
  end
60
83
  end
61
84
  end
@@ -9,8 +9,8 @@ module OpenTelemetry
9
9
  module Metrics
10
10
  module Aggregation
11
11
  # Contains the implementation of the Sum aggregation
12
- # https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#sum-aggregation
13
12
  class Sum
13
+ OVERFLOW_ATTRIBUTE_SET = { 'otel.metric.overflow' => true }.freeze
14
14
  attr_reader :exemplar_reservoir
15
15
 
16
16
  # if no reservior pass from instrument, then use this empty reservior to avoid no method found error
@@ -51,21 +51,48 @@ module OpenTelemetry
51
51
  end
52
52
  end
53
53
 
54
+ def update(increment, attributes, data_points, cardinality_limit, exemplar_offer: false)
55
+ return if @monotonic && increment < 0
56
+
57
+ # Check if we already have this attribute set
58
+ ndp = if data_points.key?(attributes)
59
+ data_points[attributes]
60
+ elsif data_points.size >= cardinality_limit - 1
61
+ data_points[OVERFLOW_ATTRIBUTE_SET] || create_new_data_point(OVERFLOW_ATTRIBUTE_SET, data_points)
62
+ else
63
+ create_new_data_point(attributes, data_points)
64
+ end
65
+
66
+ update_number_data_point(ndp, increment, exemplar_offer: exemplar_offer)
67
+ nil
68
+ end
69
+
54
70
  def monotonic?
55
71
  @monotonic
56
72
  end
57
73
 
58
- def update(increment, attributes, data_points, exemplar_offer: false)
59
- return if @monotonic && increment < 0
74
+ def aggregation_temporality
75
+ @aggregation_temporality.temporality
76
+ end
77
+
78
+ private
60
79
 
61
- ndp = data_points[attributes] || data_points[attributes] = NumberDataPoint.new(
80
+ def create_new_data_point(attributes, data_points)
81
+ data_points[attributes] = NumberDataPoint.new(
62
82
  attributes,
63
83
  nil,
64
84
  nil,
65
85
  0,
66
86
  nil
67
87
  )
88
+ end
89
+
90
+ def update_number_data_point(ndp, increment, exemplar_offer: false)
91
+ reservior_update(ndp.attributes, increment, exemplar_offer)
92
+ ndp.value += increment
93
+ end
68
94
 
95
+ def reservior_update(attributes, increment, exemplar_offer)
69
96
  reservoir = @exemplar_reservoir_storage[attributes]
70
97
  unless reservoir
71
98
  reservoir = @exemplar_reservoir.dup
@@ -73,19 +100,12 @@ module OpenTelemetry
73
100
  @exemplar_reservoir_storage[attributes] = reservoir
74
101
  end
75
102
 
76
- if exemplar_offer
77
- reservoir.offer(value: increment,
78
- timestamp: OpenTelemetry::Common::Utilities.time_in_nanoseconds,
79
- attributes: attributes,
80
- context: OpenTelemetry::Context.current)
81
- end
103
+ return unless exemplar_offer
82
104
 
83
- ndp.value += increment
84
- nil
85
- end
86
-
87
- def aggregation_temporality
88
- @aggregation_temporality.temporality
105
+ reservoir.offer(value: increment,
106
+ timestamp: OpenTelemetry::Common::Utilities.time_in_nanoseconds,
107
+ attributes: attributes,
108
+ context: OpenTelemetry::Context.current)
89
109
  end
90
110
  end
91
111
  end
@@ -12,8 +12,8 @@ module OpenTelemetry
12
12
  #
13
13
  # Potentially useful for exploratory purposes.
14
14
  class ConsoleMetricPullExporter < MetricReader
15
- def initialize
16
- super
15
+ def initialize(aggregation_cardinality_limit: nil)
16
+ super(aggregation_cardinality_limit: aggregation_cardinality_limit)
17
17
  @stopped = false
18
18
  end
19
19
 
@@ -13,8 +13,8 @@ module OpenTelemetry
13
13
  class InMemoryMetricPullExporter < MetricReader
14
14
  attr_reader :metric_snapshots
15
15
 
16
- def initialize
17
- super
16
+ def initialize(aggregation_cardinality_limit: nil)
17
+ super(aggregation_cardinality_limit: aggregation_cardinality_limit)
18
18
  @metric_snapshots = []
19
19
  @mutex = Mutex.new
20
20
  end
@@ -14,8 +14,8 @@ module OpenTelemetry
14
14
  class MetricReader
15
15
  attr_reader :metric_store
16
16
 
17
- def initialize
18
- @metric_store = OpenTelemetry::SDK::Metrics::State::MetricStore.new
17
+ def initialize(aggregation_cardinality_limit: nil)
18
+ @metric_store = OpenTelemetry::SDK::Metrics::State::MetricStore.new(cardinality_limit: aggregation_cardinality_limit)
19
19
  end
20
20
 
21
21
  def collect
@@ -24,8 +24,9 @@ module OpenTelemetry
24
24
  # @return a new instance of the {PeriodicMetricReader}.
25
25
  def initialize(export_interval_millis: Float(ENV.fetch('OTEL_METRIC_EXPORT_INTERVAL', 60_000)),
26
26
  export_timeout_millis: Float(ENV.fetch('OTEL_METRIC_EXPORT_TIMEOUT', 30_000)),
27
- exporter: nil)
28
- super()
27
+ exporter: nil,
28
+ aggregation_cardinality_limit: nil)
29
+ super(aggregation_cardinality_limit: aggregation_cardinality_limit)
29
30
 
30
31
  @export_interval = export_interval_millis / 1000.0
31
32
  @export_timeout = export_timeout_millis / 1000.0
@@ -51,17 +51,22 @@ module OpenTelemetry
51
51
 
52
52
  def invoke_callback(timeout, attributes)
53
53
  if @registered_views.empty?
54
+
55
+ resolved_cardinality_limit = @cardinality_limit || DEFAULT_CARDINALITY_LIMIT
54
56
  @mutex.synchronize do
55
57
  @callback.each do |cb|
56
58
  value = safe_guard_callback(cb, timeout: timeout)
59
+
57
60
  if value.is_a?(Numeric)
58
61
  exemplar_offer = should_offer_exemplar?(value, attributes)
59
- @default_aggregation.update(value, attributes, @data_points, exemplar_offer: exemplar_offer)
62
+ @default_aggregation.update(value, attributes, @data_points, resolved_cardinality_limit, exemplar_offer: exemplar_offer)
60
63
  end
61
64
  end
62
65
  end
63
66
  else
64
67
  @registered_views.each do |view, data_points|
68
+ resolved_cardinality_limit = resolve_cardinality_limit(view)
69
+
65
70
  @mutex.synchronize do
66
71
  @callback.each do |cb|
67
72
  value = safe_guard_callback(cb, timeout: timeout)
@@ -69,9 +74,10 @@ module OpenTelemetry
69
74
 
70
75
  merged_attributes = attributes || {}
71
76
  merged_attributes.merge!(view.attribute_keys)
77
+
72
78
  if view.valid_aggregation?
73
79
  exemplar_offer = should_offer_exemplar?(value, merged_attributes)
74
- view.aggregation.update(value, attributes, data_points, exemplar_offer: exemplar_offer)
80
+ view.aggregation.update(value, attributes, data_points, resolved_cardinality_limit, exemplar_offer: exemplar_offer)
75
81
  end
76
82
  end
77
83
  end
@@ -13,11 +13,12 @@ module OpenTelemetry
13
13
  # The MetricStore module provides SDK internal functionality that is not a part of the
14
14
  # public API.
15
15
  class MetricStore
16
- def initialize
16
+ def initialize(cardinality_limit: nil)
17
17
  @mutex = Mutex.new
18
18
  @epoch_start_time = OpenTelemetry::Common::Utilities.time_in_nanoseconds
19
19
  @epoch_end_time = nil
20
20
  @metric_streams = []
21
+ @cardinality_limit = cardinality_limit
21
22
  end
22
23
 
23
24
  def collect
@@ -32,6 +33,7 @@ module OpenTelemetry
32
33
 
33
34
  def add_metric_stream(metric_stream)
34
35
  @mutex.synchronize do
36
+ metric_stream.cardinality_limit = @cardinality_limit
35
37
  @metric_streams = @metric_streams.dup.push(metric_stream)
36
38
  nil
37
39
  end
@@ -12,9 +12,13 @@ module OpenTelemetry
12
12
  #
13
13
  # The MetricStream class provides SDK internal functionality that is not a part of the
14
14
  # public API.
15
+ #
15
16
  # rubocop:disable Metrics/ClassLength
16
17
  class MetricStream
17
18
  attr_reader :name, :description, :unit, :instrument_kind, :instrumentation_scope, :data_points
19
+ attr_writer :cardinality_limit
20
+
21
+ DEFAULT_CARDINALITY_LIMIT = 2000
18
22
 
19
23
  def initialize(
20
24
  name,
@@ -51,10 +55,14 @@ module OpenTelemetry
51
55
  return metric_data if empty_data_point?
52
56
 
53
57
  if @registered_views.empty?
54
- metric_data << aggregate_metric_data(start_time, end_time)
58
+ metric_data << aggregate_metric_data(start_time,
59
+ end_time)
55
60
  else
56
61
  @registered_views.each do |view, data_points|
57
- metric_data << aggregate_metric_data(start_time, end_time, aggregation: view.aggregation, data_points: data_points)
62
+ metric_data << aggregate_metric_data(start_time,
63
+ end_time,
64
+ aggregation: view.aggregation,
65
+ data_points: data_points)
58
66
  end
59
67
  end
60
68
 
@@ -62,20 +70,29 @@ module OpenTelemetry
62
70
  end
63
71
  end
64
72
 
73
+ # view has the cardinality, pass to aggregation update
74
+ # to determine if aggregation have the cardinality
75
+ # if the aggregation does not have the cardinality, then it will be default 2000
76
+ # it better to move overflowed data_points during update because if do it in collect,
77
+ # then we need to sort the entire data_points (~ 2000) based on time, which is time-consuming
78
+ # view will modify the data_point that is not suitable when there are multiple views
65
79
  def update(value, attributes)
66
80
  if @registered_views.empty?
81
+ resolved_cardinality_limit = resolve_cardinality_limit(nil)
67
82
  @mutex.synchronize do
68
83
  exemplar_offer = should_offer_exemplar?(value, attributes)
69
- @default_aggregation.update(value, attributes, @data_points, exemplar_offer: exemplar_offer)
84
+ @default_aggregation.update(value, attributes, @data_points, resolved_cardinality_limit, exemplar_offer: exemplar_offer)
70
85
  end
71
86
  else
72
87
  @registered_views.each do |view, data_points|
88
+ resolved_cardinality_limit = resolve_cardinality_limit(view)
73
89
  @mutex.synchronize do
74
90
  attributes ||= {}
75
91
  attributes.merge!(view.attribute_keys)
92
+
76
93
  if view.valid_aggregation?
77
94
  exemplar_offer = should_offer_exemplar?(value, attributes)
78
- view.aggregation.update(value, attributes, data_points, exemplar_offer: exemplar_offer)
95
+ view.aggregation.update(value, attributes, data_points, resolved_cardinality_limit, exemplar_offer: exemplar_offer)
79
96
  end
80
97
  end
81
98
  end
@@ -119,6 +136,11 @@ module OpenTelemetry
119
136
  end
120
137
  end
121
138
 
139
+ def resolve_cardinality_limit(view)
140
+ cardinality_limit = view&.aggregation_cardinality_limit || @cardinality_limit || DEFAULT_CARDINALITY_LIMIT
141
+ [cardinality_limit, 0].max # if cardinality_limit is negative, then give it 0
142
+ end
143
+
122
144
  def should_offer_exemplar?(value, attributes)
123
145
  return false if @exemplar_reservoir&.noop?
124
146
 
@@ -8,7 +8,7 @@ module OpenTelemetry
8
8
  module SDK
9
9
  module Metrics
10
10
  # Current OpenTelemetry metrics sdk version
11
- VERSION = '0.14.0'
11
+ VERSION = '0.15.0'
12
12
  end
13
13
  end
14
14
  end
@@ -10,13 +10,14 @@ module OpenTelemetry
10
10
  module View
11
11
  # RegisteredView is an internal class used to match Views with a given {MetricStream}
12
12
  class RegisteredView
13
- attr_reader :name, :aggregation, :attribute_keys, :regex
13
+ attr_reader :name, :aggregation, :attribute_keys, :regex, :aggregation_cardinality_limit
14
14
 
15
15
  def initialize(name, **options)
16
16
  @name = name
17
17
  @options = options
18
18
  @aggregation = options[:aggregation]
19
19
  @attribute_keys = options[:attribute_keys] || {}
20
+ @aggregation_cardinality_limit = options[:aggregation_cardinality_limit]
20
21
 
21
22
  generate_regex_pattern(name)
22
23
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: opentelemetry-metrics-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.14.0
4
+ version: 0.15.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - OpenTelemetry Authors
@@ -51,104 +51,6 @@ dependencies:
51
51
  - - "~>"
52
52
  - !ruby/object:Gem::Version
53
53
  version: '1.2'
54
- - !ruby/object:Gem::Dependency
55
- name: minitest
56
- requirement: !ruby/object:Gem::Requirement
57
- requirements:
58
- - - "~>"
59
- - !ruby/object:Gem::Version
60
- version: '5.0'
61
- type: :development
62
- prerelease: false
63
- version_requirements: !ruby/object:Gem::Requirement
64
- requirements:
65
- - - "~>"
66
- - !ruby/object:Gem::Version
67
- version: '5.0'
68
- - !ruby/object:Gem::Dependency
69
- name: opentelemetry-test-helpers
70
- requirement: !ruby/object:Gem::Requirement
71
- requirements:
72
- - - ">="
73
- - !ruby/object:Gem::Version
74
- version: '0'
75
- type: :development
76
- prerelease: false
77
- version_requirements: !ruby/object:Gem::Requirement
78
- requirements:
79
- - - ">="
80
- - !ruby/object:Gem::Version
81
- version: '0'
82
- - !ruby/object:Gem::Dependency
83
- name: rake
84
- requirement: !ruby/object:Gem::Requirement
85
- requirements:
86
- - - "~>"
87
- - !ruby/object:Gem::Version
88
- version: '13.3'
89
- type: :development
90
- prerelease: false
91
- version_requirements: !ruby/object:Gem::Requirement
92
- requirements:
93
- - - "~>"
94
- - !ruby/object:Gem::Version
95
- version: '13.3'
96
- - !ruby/object:Gem::Dependency
97
- name: rubocop
98
- requirement: !ruby/object:Gem::Requirement
99
- requirements:
100
- - - "~>"
101
- - !ruby/object:Gem::Version
102
- version: '1.65'
103
- type: :development
104
- prerelease: false
105
- version_requirements: !ruby/object:Gem::Requirement
106
- requirements:
107
- - - "~>"
108
- - !ruby/object:Gem::Version
109
- version: '1.65'
110
- - !ruby/object:Gem::Dependency
111
- name: simplecov
112
- requirement: !ruby/object:Gem::Requirement
113
- requirements:
114
- - - "~>"
115
- - !ruby/object:Gem::Version
116
- version: '0.17'
117
- type: :development
118
- prerelease: false
119
- version_requirements: !ruby/object:Gem::Requirement
120
- requirements:
121
- - - "~>"
122
- - !ruby/object:Gem::Version
123
- version: '0.17'
124
- - !ruby/object:Gem::Dependency
125
- name: yard
126
- requirement: !ruby/object:Gem::Requirement
127
- requirements:
128
- - - "~>"
129
- - !ruby/object:Gem::Version
130
- version: '0.9'
131
- type: :development
132
- prerelease: false
133
- version_requirements: !ruby/object:Gem::Requirement
134
- requirements:
135
- - - "~>"
136
- - !ruby/object:Gem::Version
137
- version: '0.9'
138
- - !ruby/object:Gem::Dependency
139
- name: yard-doctest
140
- requirement: !ruby/object:Gem::Requirement
141
- requirements:
142
- - - "~>"
143
- - !ruby/object:Gem::Version
144
- version: 0.1.6
145
- type: :development
146
- prerelease: false
147
- version_requirements: !ruby/object:Gem::Requirement
148
- requirements:
149
- - - "~>"
150
- - !ruby/object:Gem::Version
151
- version: 0.1.6
152
54
  description: A stats collection and distributed tracing framework
153
55
  email:
154
56
  - cncf-opentelemetry-contributors@lists.cncf.io
@@ -219,10 +121,10 @@ homepage: https://github.com/open-telemetry/opentelemetry-ruby
219
121
  licenses:
220
122
  - Apache-2.0
221
123
  metadata:
222
- changelog_uri: https://open-telemetry.github.io/opentelemetry-ruby/opentelemetry-metrics-sdk/v0.14.0/file.CHANGELOG.html
223
- source_code_uri: https://github.com/open-telemetry/opentelemetry-ruby/tree/opentelemetry-metrics-sdk/v0.14.0/metrics_sdk
124
+ changelog_uri: https://rubydoc.info/gems/opentelemetry-metrics-sdk/0.15.0/file/CHANGELOG.md
125
+ source_code_uri: https://github.com/open-telemetry/opentelemetry-ruby/tree/opentelemetry-metrics-sdk/v0.15.0/metrics_sdk
224
126
  bug_tracker_uri: https://github.com/open-telemetry/opentelemetry-ruby/issues
225
- documentation_uri: https://open-telemetry.github.io/opentelemetry-ruby/opentelemetry-metrics-sdk/v0.14.0
127
+ documentation_uri: https://rubydoc.info/gems/opentelemetry-metrics-sdk/0.15.0
226
128
  rdoc_options: []
227
129
  require_paths:
228
130
  - lib
@@ -237,7 +139,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
237
139
  - !ruby/object:Gem::Version
238
140
  version: '0'
239
141
  requirements: []
240
- rubygems_version: 4.0.6
142
+ rubygems_version: 4.0.10
241
143
  specification_version: 4
242
144
  summary: A stats collection and distributed tracing framework
243
145
  test_files: []