opentelemetry-metrics-sdk 0.18.0 → 0.19.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 +4 -4
- data/CHANGELOG.md +10 -0
- data/lib/opentelemetry/sdk/metrics/aggregation/explicit_bucket_histogram.rb +13 -11
- data/lib/opentelemetry/sdk/metrics/aggregation/exponential_bucket_histogram.rb +89 -79
- data/lib/opentelemetry/sdk/metrics/aggregation/last_value.rb +12 -8
- data/lib/opentelemetry/sdk/metrics/aggregation/stream_scoped_storage.rb +26 -0
- data/lib/opentelemetry/sdk/metrics/aggregation/sum.rb +15 -11
- data/lib/opentelemetry/sdk/metrics/aggregation.rb +1 -0
- data/lib/opentelemetry/sdk/metrics/export/metric_reader.rb +15 -0
- data/lib/opentelemetry/sdk/metrics/instrument/asynchronous_instrument.rb +3 -1
- data/lib/opentelemetry/sdk/metrics/meter.rb +5 -9
- data/lib/opentelemetry/sdk/metrics/meter_provider.rb +18 -0
- data/lib/opentelemetry/sdk/metrics/version.rb +1 -1
- metadata +6 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3f876db9fe6efa57838509b6ffc7e763f08f752f195169266d47fb5e87d7fba6
|
|
4
|
+
data.tar.gz: 397d5c293f18de20edb1c939cd750f4149a9944e70cd9b4c1f411559a4488bdd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e541f4daaa1e6c0228e22d297f18239e743917b16dcd047b9dfd2a87954fa68b0cfb1ba1401ca1274d7183610088790d6e45d9d79325859919673f4c31312083
|
|
7
|
+
data.tar.gz: cf8568c155bd447989b1082b0dc956d2f75fc7cfa2b333580c5067b4cf72ec01effdef928081f945dad097c6ffe170f2ab2274444cf5ac9780393617ccb1b9cc
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Release History: opentelemetry-metrics-sdk
|
|
2
2
|
|
|
3
|
+
### v0.19.0 / 2026-09-29
|
|
4
|
+
|
|
5
|
+
* BREAKING CHANGE: Update api for spec compliance (#2309)
|
|
6
|
+
* ADDED: Update api for spec compliance (#2309)
|
|
7
|
+
* ADDED: Move otlp exporter uri builder to common (#2240)
|
|
8
|
+
* FIXED: Scope shared aggregation state to the owning stream (#2300) (#2314)
|
|
9
|
+
* FIXED: Size exponential histogram exemplars (#2392)
|
|
10
|
+
* FIXED: Accept arbitrary instrument metadata (#2395)
|
|
11
|
+
* FIXED: Prevent readers spanning providers (#2393)
|
|
12
|
+
|
|
3
13
|
### v0.18.0 / 2026-09-09
|
|
4
14
|
|
|
5
15
|
* BREAKING CHANGE: Align with the spec for metrics sdk and metrics otlp exporter (#2301)
|
|
@@ -11,6 +11,8 @@ 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 # rubocop:disable Metrics/ClassLength
|
|
14
|
+
include StreamScopedStorage
|
|
15
|
+
|
|
14
16
|
OVERFLOW_ATTRIBUTE_SET = { 'otel.metric.overflow' => true }.freeze
|
|
15
17
|
attr_reader :exemplar_reservoir
|
|
16
18
|
|
|
@@ -31,7 +33,7 @@ module OpenTelemetry
|
|
|
31
33
|
@boundaries = boundaries && !boundaries.empty? ? boundaries.sort : nil
|
|
32
34
|
@record_min_max = record_min_max
|
|
33
35
|
@exemplar_reservoir = exemplar_reservoir || Metrics::Exemplar::AlignedHistogramBucketExemplarReservoir.new(boundaries: @boundaries)
|
|
34
|
-
@exemplar_reservoir_storage =
|
|
36
|
+
@exemplar_reservoir_storage = new_stream_storage
|
|
35
37
|
end
|
|
36
38
|
|
|
37
39
|
# Returns the current histogram data points, clearing them for delta temporality.
|
|
@@ -41,8 +43,8 @@ module OpenTelemetry
|
|
|
41
43
|
hdps = data_points.values.map! do |hdp|
|
|
42
44
|
hdp.start_time_unix_nano = start_time
|
|
43
45
|
hdp.time_unix_nano = end_time
|
|
44
|
-
reservoir = @exemplar_reservoir_storage[hdp.attributes]
|
|
45
|
-
hdp.exemplars = reservoir&.collect(attributes: hdp.attributes, aggregation_temporality: @aggregation_temporality)
|
|
46
|
+
reservoir = @exemplar_reservoir_storage[data_points][hdp.attributes]
|
|
47
|
+
hdp.exemplars = reservoir&.collect(attributes: hdp.attributes, aggregation_temporality: @aggregation_temporality.temporality)
|
|
46
48
|
hdp
|
|
47
49
|
end
|
|
48
50
|
data_points.clear
|
|
@@ -52,8 +54,8 @@ module OpenTelemetry
|
|
|
52
54
|
data_points.values.map! do |hdp|
|
|
53
55
|
hdp.start_time_unix_nano ||= start_time # Start time of a data point is from the first observation.
|
|
54
56
|
hdp.time_unix_nano = end_time
|
|
55
|
-
reservoir = @exemplar_reservoir_storage[hdp.attributes]
|
|
56
|
-
hdp.exemplars = reservoir&.collect(attributes: hdp.attributes, aggregation_temporality: @aggregation_temporality)
|
|
57
|
+
reservoir = @exemplar_reservoir_storage[data_points][hdp.attributes]
|
|
58
|
+
hdp.exemplars = reservoir&.collect(attributes: hdp.attributes, aggregation_temporality: @aggregation_temporality.temporality)
|
|
57
59
|
hdp = hdp.dup
|
|
58
60
|
hdp.bucket_counts = hdp.bucket_counts.dup
|
|
59
61
|
hdp
|
|
@@ -71,7 +73,7 @@ module OpenTelemetry
|
|
|
71
73
|
create_new_data_point(attributes, data_points)
|
|
72
74
|
end
|
|
73
75
|
|
|
74
|
-
update_histogram_data_point(hdp, amount, exemplar_offer: exemplar_offer)
|
|
76
|
+
update_histogram_data_point(hdp, amount, data_points, exemplar_offer: exemplar_offer)
|
|
75
77
|
nil
|
|
76
78
|
end
|
|
77
79
|
|
|
@@ -103,8 +105,8 @@ module OpenTelemetry
|
|
|
103
105
|
)
|
|
104
106
|
end
|
|
105
107
|
|
|
106
|
-
def update_histogram_data_point(hdp, amount, exemplar_offer: false)
|
|
107
|
-
reservior_update(hdp.attributes, amount, exemplar_offer)
|
|
108
|
+
def update_histogram_data_point(hdp, amount, stream_key, exemplar_offer: false)
|
|
109
|
+
reservior_update(hdp.attributes, amount, exemplar_offer, stream_key)
|
|
108
110
|
|
|
109
111
|
if @record_min_max
|
|
110
112
|
hdp.max = amount if amount > hdp.max
|
|
@@ -119,12 +121,12 @@ module OpenTelemetry
|
|
|
119
121
|
hdp.bucket_counts[bucket_index] += 1
|
|
120
122
|
end
|
|
121
123
|
|
|
122
|
-
def reservior_update(attributes, amount, exemplar_offer)
|
|
123
|
-
reservoir = @exemplar_reservoir_storage[attributes]
|
|
124
|
+
def reservior_update(attributes, amount, exemplar_offer, stream_key)
|
|
125
|
+
reservoir = @exemplar_reservoir_storage[stream_key][attributes]
|
|
124
126
|
unless reservoir
|
|
125
127
|
reservoir = @exemplar_reservoir.dup
|
|
126
128
|
reservoir.reset
|
|
127
|
-
@exemplar_reservoir_storage[attributes] = reservoir
|
|
129
|
+
@exemplar_reservoir_storage[stream_key][attributes] = reservoir
|
|
128
130
|
end
|
|
129
131
|
|
|
130
132
|
return unless exemplar_offer
|
|
@@ -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
|
+
include StreamScopedStorage
|
|
21
|
+
|
|
20
22
|
OVERFLOW_ATTRIBUTE_SET = { 'otel.metric.overflow' => true }.freeze
|
|
21
23
|
|
|
22
24
|
# relate to min max scale: https://opentelemetry.io/docs/specs/otel/metrics/sdk/#support-a-minimum-and-maximum-scale
|
|
@@ -29,10 +31,6 @@ module OpenTelemetry
|
|
|
29
31
|
|
|
30
32
|
attr_reader :exemplar_reservoir
|
|
31
33
|
|
|
32
|
-
# if no reservoir pass from instrument, then use this empty reservoir to avoid no method found error
|
|
33
|
-
DEFAULT_RESERVOIR = Metrics::Exemplar::SimpleFixedSizeExemplarReservoir.new
|
|
34
|
-
private_constant :DEFAULT_RESERVOIR
|
|
35
|
-
|
|
36
34
|
# The default boundaries are calculated based on default max_size and max_scale values
|
|
37
35
|
def initialize(
|
|
38
36
|
aggregation_temporality: ENV.fetch('OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE', :delta),
|
|
@@ -53,46 +51,58 @@ module OpenTelemetry
|
|
|
53
51
|
@size = validate_size(max_size)
|
|
54
52
|
@scale = validate_scale(max_scale)
|
|
55
53
|
|
|
56
|
-
@exemplar_reservoir = exemplar_reservoir ||
|
|
57
|
-
@exemplar_reservoir_storage =
|
|
54
|
+
@exemplar_reservoir = exemplar_reservoir || Metrics::Exemplar::SimpleFixedSizeExemplarReservoir.new(max_size: [20, @size].min)
|
|
55
|
+
@exemplar_reservoir_storage = new_stream_storage
|
|
58
56
|
|
|
59
57
|
@mapping = new_mapping(@scale)
|
|
60
58
|
|
|
61
59
|
# Previous state for cumulative aggregation
|
|
62
|
-
@previous_positive =
|
|
63
|
-
@previous_negative =
|
|
64
|
-
@previous_min =
|
|
65
|
-
@previous_max =
|
|
66
|
-
@previous_sum =
|
|
67
|
-
@previous_count =
|
|
68
|
-
@previous_zero_count =
|
|
69
|
-
@previous_scale =
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
@
|
|
73
|
-
@previous_mappings = {}
|
|
60
|
+
@previous_positive = new_stream_storage # nil
|
|
61
|
+
@previous_negative = new_stream_storage # nil
|
|
62
|
+
@previous_min = new_stream_storage # Float::INFINITY
|
|
63
|
+
@previous_max = new_stream_storage # -Float::INFINITY
|
|
64
|
+
@previous_sum = new_stream_storage # 0
|
|
65
|
+
@previous_count = new_stream_storage # 0
|
|
66
|
+
@previous_zero_count = new_stream_storage # 0
|
|
67
|
+
@previous_scale = new_stream_storage # nil
|
|
68
|
+
|
|
69
|
+
@mappings = new_stream_storage
|
|
70
|
+
@previous_mappings = new_stream_storage
|
|
74
71
|
end
|
|
75
72
|
|
|
76
73
|
# when aggregation temporality is cumulative, merge and downscale will happen.
|
|
77
74
|
# rubocop:disable-next Metrics/MethodLength
|
|
78
75
|
def collect(start_time, end_time, data_points)
|
|
76
|
+
stream_key = data_points
|
|
77
|
+
stream_exemplar_reservoir_storage = @exemplar_reservoir_storage[stream_key]
|
|
78
|
+
|
|
79
79
|
if @aggregation_temporality.delta?
|
|
80
80
|
# Set timestamps and 'move' data point values to result.
|
|
81
81
|
hdps = data_points.values.map! do |hdp|
|
|
82
82
|
hdp.start_time_unix_nano = start_time
|
|
83
83
|
hdp.time_unix_nano = end_time
|
|
84
|
-
reservoir =
|
|
85
|
-
hdp.exemplars = reservoir&.collect(attributes: hdp.attributes, aggregation_temporality: @aggregation_temporality)
|
|
84
|
+
reservoir = stream_exemplar_reservoir_storage[hdp.attributes]
|
|
85
|
+
hdp.exemplars = reservoir&.collect(attributes: hdp.attributes, aggregation_temporality: @aggregation_temporality.temporality)
|
|
86
86
|
hdp
|
|
87
87
|
end
|
|
88
88
|
data_points.clear
|
|
89
|
-
@mappings.
|
|
89
|
+
@mappings.delete(stream_key)
|
|
90
90
|
hdps
|
|
91
91
|
else
|
|
92
92
|
# CUMULATIVE temporality - merge current data_points to previous data_points
|
|
93
93
|
# and only keep the merged data_points in @previous_*
|
|
94
94
|
|
|
95
95
|
merged_data_points = {}
|
|
96
|
+
stream_previous_positive = @previous_positive[stream_key]
|
|
97
|
+
stream_previous_negative = @previous_negative[stream_key]
|
|
98
|
+
stream_previous_min = @previous_min[stream_key]
|
|
99
|
+
stream_previous_max = @previous_max[stream_key]
|
|
100
|
+
stream_previous_sum = @previous_sum[stream_key]
|
|
101
|
+
stream_previous_count = @previous_count[stream_key]
|
|
102
|
+
stream_previous_zero_count = @previous_zero_count[stream_key]
|
|
103
|
+
stream_previous_scale = @previous_scale[stream_key]
|
|
104
|
+
stream_mappings = @mappings[stream_key]
|
|
105
|
+
stream_previous_mappings = @previous_mappings[stream_key]
|
|
96
106
|
|
|
97
107
|
# this will slow down the operation especially if large amount of data_points present
|
|
98
108
|
# but it should be fine since with cumulative, the data_points are merged into previous_* and not kept in data_points
|
|
@@ -108,25 +118,25 @@ module OpenTelemetry
|
|
|
108
118
|
current_scale = hdp.scale
|
|
109
119
|
|
|
110
120
|
# Setup previous positive, negative bucket and scale based on three different cases
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
121
|
+
stream_previous_positive[attributes] = current_positive.copy_empty if stream_previous_positive[attributes].nil?
|
|
122
|
+
stream_previous_negative[attributes] = current_negative.copy_empty if stream_previous_negative[attributes].nil?
|
|
123
|
+
stream_previous_scale[attributes] = current_scale if stream_previous_scale[attributes].nil?
|
|
114
124
|
|
|
115
125
|
# Determine minimum scale for merging
|
|
116
|
-
min_scale = [
|
|
126
|
+
min_scale = [stream_previous_scale[attributes], current_scale].min
|
|
117
127
|
|
|
118
128
|
# Calculate ranges for positive and negative buckets
|
|
119
129
|
low_positive, high_positive = get_low_high_previous_current(
|
|
120
|
-
|
|
130
|
+
stream_previous_positive[attributes],
|
|
121
131
|
current_positive,
|
|
122
|
-
|
|
132
|
+
stream_previous_scale[attributes],
|
|
123
133
|
current_scale,
|
|
124
134
|
min_scale
|
|
125
135
|
)
|
|
126
136
|
low_negative, high_negative = get_low_high_previous_current(
|
|
127
|
-
|
|
137
|
+
stream_previous_negative[attributes],
|
|
128
138
|
current_negative,
|
|
129
|
-
|
|
139
|
+
stream_previous_scale[attributes],
|
|
130
140
|
current_scale,
|
|
131
141
|
min_scale
|
|
132
142
|
)
|
|
@@ -138,79 +148,79 @@ module OpenTelemetry
|
|
|
138
148
|
].min
|
|
139
149
|
|
|
140
150
|
# Downscale previous buckets if necessary
|
|
141
|
-
downscale_change =
|
|
142
|
-
downscale(downscale_change,
|
|
151
|
+
downscale_change = stream_previous_scale[attributes] - min_scale
|
|
152
|
+
downscale(downscale_change, stream_previous_positive[attributes], stream_previous_negative[attributes])
|
|
143
153
|
|
|
144
154
|
# Merge current buckets into previous buckets (kind like update); it's always :cumulative
|
|
145
|
-
merge_buckets(
|
|
146
|
-
merge_buckets(
|
|
155
|
+
merge_buckets(stream_previous_positive[attributes], current_positive, current_scale, min_scale, @aggregation_temporality)
|
|
156
|
+
merge_buckets(stream_previous_negative[attributes], current_negative, current_scale, min_scale, @aggregation_temporality)
|
|
147
157
|
|
|
148
158
|
# initialize min, max, sum, count, zero_count for first time
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
159
|
+
stream_previous_min[attributes] = Float::INFINITY if stream_previous_min[attributes].nil?
|
|
160
|
+
stream_previous_max[attributes] = -Float::INFINITY if stream_previous_max[attributes].nil?
|
|
161
|
+
stream_previous_sum[attributes] = 0 if stream_previous_sum[attributes].nil?
|
|
162
|
+
stream_previous_count[attributes] = 0 if stream_previous_count[attributes].nil?
|
|
163
|
+
stream_previous_zero_count[attributes] = 0 if stream_previous_zero_count[attributes].nil?
|
|
154
164
|
|
|
155
165
|
# Update aggregated values
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
166
|
+
stream_previous_min[attributes] = [stream_previous_min[attributes], current_min].min
|
|
167
|
+
stream_previous_max[attributes] = [stream_previous_max[attributes], current_max].max
|
|
168
|
+
stream_previous_sum[attributes] += current_sum
|
|
169
|
+
stream_previous_count[attributes] += current_count
|
|
170
|
+
stream_previous_zero_count[attributes] += current_zero_count
|
|
171
|
+
stream_previous_scale[attributes] = min_scale
|
|
162
172
|
|
|
163
173
|
# Create merged data point
|
|
164
|
-
reservoir =
|
|
174
|
+
reservoir = stream_exemplar_reservoir_storage[attributes]
|
|
165
175
|
merged_hdp = ExponentialHistogramDataPoint.new(
|
|
166
176
|
attributes,
|
|
167
177
|
start_time,
|
|
168
178
|
end_time,
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
179
|
+
stream_previous_count[attributes],
|
|
180
|
+
stream_previous_sum[attributes],
|
|
181
|
+
stream_previous_scale[attributes],
|
|
182
|
+
stream_previous_zero_count[attributes],
|
|
183
|
+
stream_previous_positive[attributes].dup,
|
|
184
|
+
stream_previous_negative[attributes].dup,
|
|
175
185
|
0, # flags
|
|
176
|
-
reservoir&.collect(attributes: attributes, aggregation_temporality: @aggregation_temporality), # exemplars
|
|
177
|
-
|
|
178
|
-
|
|
186
|
+
reservoir&.collect(attributes: attributes, aggregation_temporality: @aggregation_temporality.temporality), # exemplars
|
|
187
|
+
stream_previous_min[attributes],
|
|
188
|
+
stream_previous_max[attributes],
|
|
179
189
|
@zero_threshold
|
|
180
190
|
)
|
|
181
191
|
|
|
182
192
|
merged_data_points[attributes] = merged_hdp
|
|
183
|
-
|
|
193
|
+
stream_previous_mappings[attributes] = stream_mappings[attributes] if stream_mappings[attributes] # Preserve mapping for next collection
|
|
184
194
|
end
|
|
185
195
|
|
|
186
196
|
# when you have no local_data_points, the loop from cumulative aggregation will not run
|
|
187
197
|
# so return last merged data points if exists
|
|
188
|
-
if data_points.empty? &&
|
|
189
|
-
|
|
190
|
-
reservoir =
|
|
198
|
+
if data_points.empty? && !stream_previous_positive.empty?
|
|
199
|
+
stream_previous_positive.each_key do |attributes|
|
|
200
|
+
reservoir = stream_exemplar_reservoir_storage[attributes]
|
|
191
201
|
merged_hdp = ExponentialHistogramDataPoint.new(
|
|
192
202
|
attributes,
|
|
193
203
|
start_time,
|
|
194
204
|
end_time,
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
205
|
+
stream_previous_count[attributes],
|
|
206
|
+
stream_previous_sum[attributes],
|
|
207
|
+
stream_previous_scale[attributes],
|
|
208
|
+
stream_previous_zero_count[attributes],
|
|
209
|
+
stream_previous_positive[attributes].dup,
|
|
210
|
+
stream_previous_negative[attributes].dup,
|
|
201
211
|
0, # flags
|
|
202
|
-
reservoir&.collect(attributes: attributes, aggregation_temporality: @aggregation_temporality), # exemplars
|
|
203
|
-
|
|
204
|
-
|
|
212
|
+
reservoir&.collect(attributes: attributes, aggregation_temporality: @aggregation_temporality.temporality), # exemplars
|
|
213
|
+
stream_previous_min[attributes],
|
|
214
|
+
stream_previous_max[attributes],
|
|
205
215
|
@zero_threshold
|
|
206
216
|
)
|
|
207
217
|
merged_data_points[attributes] = merged_hdp
|
|
208
218
|
end
|
|
209
219
|
end
|
|
210
220
|
|
|
211
|
-
# Swap current with previous mappings for next cycle
|
|
212
|
-
@mappings =
|
|
213
|
-
@previous_mappings = {}
|
|
221
|
+
# Swap current with previous mappings for next cycle, for this stream only
|
|
222
|
+
@mappings[stream_key] = stream_previous_mappings
|
|
223
|
+
@previous_mappings[stream_key] = {}
|
|
214
224
|
|
|
215
225
|
# clear data_points since the data is merged into previous_* already;
|
|
216
226
|
# otherwise we will have duplicated data_points in the next collect
|
|
@@ -229,7 +239,7 @@ module OpenTelemetry
|
|
|
229
239
|
create_new_data_point(attributes, data_points)
|
|
230
240
|
end
|
|
231
241
|
|
|
232
|
-
update_histogram_data_point(hdp, attributes, amount, exemplar_offer: exemplar_offer)
|
|
242
|
+
update_histogram_data_point(hdp, attributes, amount, data_points, exemplar_offer: exemplar_offer)
|
|
233
243
|
nil
|
|
234
244
|
end
|
|
235
245
|
|
|
@@ -265,8 +275,8 @@ module OpenTelemetry
|
|
|
265
275
|
end
|
|
266
276
|
|
|
267
277
|
# rubocop:disable-next Metrics/CyclomaticComplexity,Metrics/MethodLength
|
|
268
|
-
def update_histogram_data_point(hdp, attributes, amount, exemplar_offer: false)
|
|
269
|
-
reservior_update(attributes, amount, exemplar_offer)
|
|
278
|
+
def update_histogram_data_point(hdp, attributes, amount, stream_key, exemplar_offer: false)
|
|
279
|
+
reservior_update(attributes, amount, exemplar_offer, stream_key)
|
|
270
280
|
|
|
271
281
|
if @record_min_max
|
|
272
282
|
hdp.max = amount if amount > hdp.max
|
|
@@ -289,11 +299,11 @@ module OpenTelemetry
|
|
|
289
299
|
# Reset scale to max_scale if transitioning from all-zeros to first non-zero value
|
|
290
300
|
if buckets.counts == [0] && hdp.scale == 0 && hdp.count > hdp.zero_count
|
|
291
301
|
hdp.scale = @scale
|
|
292
|
-
@mappings.delete(attributes) # Clear any cached mapping
|
|
302
|
+
@mappings[stream_key].delete(attributes) # Clear any cached mapping
|
|
293
303
|
end
|
|
294
304
|
|
|
295
305
|
# Get or create mapping for this attribute set
|
|
296
|
-
mapping = @mappings[attributes] ||= new_mapping(hdp.scale)
|
|
306
|
+
mapping = @mappings[stream_key][attributes] ||= new_mapping(hdp.scale)
|
|
297
307
|
bucket_index = mapping.map_to_index(amount)
|
|
298
308
|
|
|
299
309
|
rescaling_needed = false
|
|
@@ -320,7 +330,7 @@ module OpenTelemetry
|
|
|
320
330
|
downscale(scale_change, hdp.positive, hdp.negative)
|
|
321
331
|
new_scale = mapping.scale - scale_change
|
|
322
332
|
mapping = new_mapping(new_scale)
|
|
323
|
-
@mappings[attributes] = mapping # Update cache
|
|
333
|
+
@mappings[stream_key][attributes] = mapping # Update cache
|
|
324
334
|
bucket_index = mapping.map_to_index(amount)
|
|
325
335
|
|
|
326
336
|
OpenTelemetry.logger.debug "Rescaled with new scale #{new_scale} from #{low} and #{high}; bucket_index is updated to #{bucket_index}"
|
|
@@ -352,12 +362,12 @@ module OpenTelemetry
|
|
|
352
362
|
buckets.grow(span + 1, @size)
|
|
353
363
|
end
|
|
354
364
|
|
|
355
|
-
def reservior_update(attributes, amount, exemplar_offer)
|
|
356
|
-
reservoir = @exemplar_reservoir_storage[attributes]
|
|
365
|
+
def reservior_update(attributes, amount, exemplar_offer, stream_key)
|
|
366
|
+
reservoir = @exemplar_reservoir_storage[stream_key][attributes]
|
|
357
367
|
unless reservoir
|
|
358
368
|
reservoir = @exemplar_reservoir.dup
|
|
359
369
|
reservoir.reset
|
|
360
|
-
@exemplar_reservoir_storage[attributes] = reservoir
|
|
370
|
+
@exemplar_reservoir_storage[stream_key][attributes] = reservoir
|
|
361
371
|
end
|
|
362
372
|
|
|
363
373
|
return unless exemplar_offer
|
|
@@ -10,6 +10,8 @@ module OpenTelemetry
|
|
|
10
10
|
module Aggregation
|
|
11
11
|
# Contains the implementation of the LastValue aggregation
|
|
12
12
|
class LastValue
|
|
13
|
+
include StreamScopedStorage
|
|
14
|
+
|
|
13
15
|
OVERFLOW_ATTRIBUTE_SET = { 'otel.metric.overflow' => true }.freeze
|
|
14
16
|
attr_reader :exemplar_reservoir
|
|
15
17
|
|
|
@@ -19,15 +21,17 @@ module OpenTelemetry
|
|
|
19
21
|
|
|
20
22
|
def initialize(exemplar_reservoir: nil)
|
|
21
23
|
@exemplar_reservoir = exemplar_reservoir || DEFAULT_RESERVOIR
|
|
22
|
-
@exemplar_reservoir_storage =
|
|
24
|
+
@exemplar_reservoir_storage = new_stream_storage
|
|
23
25
|
end
|
|
24
26
|
|
|
25
27
|
# Returns the current data points, clearing them for delta temporality.
|
|
26
28
|
def collect(start_time, end_time, data_points)
|
|
29
|
+
stream_exemplar_reservoir_storage = @exemplar_reservoir_storage[data_points]
|
|
30
|
+
|
|
27
31
|
ndps = data_points.values.map! do |ndp|
|
|
28
32
|
ndp.start_time_unix_nano = start_time
|
|
29
33
|
ndp.time_unix_nano = end_time
|
|
30
|
-
reservoir =
|
|
34
|
+
reservoir = stream_exemplar_reservoir_storage[ndp.attributes]
|
|
31
35
|
ndp.exemplars = reservoir&.collect(attributes: ndp.attributes, aggregation_temporality: :delta)
|
|
32
36
|
ndp
|
|
33
37
|
end
|
|
@@ -46,7 +50,7 @@ module OpenTelemetry
|
|
|
46
50
|
create_new_data_point(attributes, data_points)
|
|
47
51
|
end
|
|
48
52
|
|
|
49
|
-
update_number_data_point(ndp, increment, exemplar_offer: exemplar_offer)
|
|
53
|
+
update_number_data_point(ndp, increment, data_points, exemplar_offer: exemplar_offer)
|
|
50
54
|
nil
|
|
51
55
|
end
|
|
52
56
|
|
|
@@ -63,17 +67,17 @@ module OpenTelemetry
|
|
|
63
67
|
)
|
|
64
68
|
end
|
|
65
69
|
|
|
66
|
-
def update_number_data_point(ndp, increment, exemplar_offer: false)
|
|
70
|
+
def update_number_data_point(ndp, increment, stream_key, exemplar_offer: false)
|
|
67
71
|
ndp.value = increment
|
|
68
|
-
reservior_update(ndp.attributes, increment, exemplar_offer)
|
|
72
|
+
reservior_update(ndp.attributes, increment, exemplar_offer, stream_key)
|
|
69
73
|
end
|
|
70
74
|
|
|
71
|
-
def reservior_update(attributes, increment, exemplar_offer)
|
|
72
|
-
reservoir = @exemplar_reservoir_storage[attributes]
|
|
75
|
+
def reservior_update(attributes, increment, exemplar_offer, stream_key)
|
|
76
|
+
reservoir = @exemplar_reservoir_storage[stream_key][attributes]
|
|
73
77
|
unless reservoir
|
|
74
78
|
reservoir = @exemplar_reservoir.dup
|
|
75
79
|
reservoir.reset
|
|
76
|
-
@exemplar_reservoir_storage[attributes] = reservoir
|
|
80
|
+
@exemplar_reservoir_storage[stream_key][attributes] = reservoir
|
|
77
81
|
end
|
|
78
82
|
|
|
79
83
|
return unless exemplar_offer
|
|
@@ -0,0 +1,26 @@
|
|
|
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
|
+
# Builds the nested storage aggregations use to keep per-stream state
|
|
12
|
+
# apart, since a wildcard or regex view shares one aggregation instance
|
|
13
|
+
# across every stream it matches.
|
|
14
|
+
module StreamScopedStorage
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
# Streams are keyed by their +data_points+ hash, which is mutable, so
|
|
18
|
+
# the outer hash must compare by identity rather than by value.
|
|
19
|
+
def new_stream_storage
|
|
20
|
+
Hash.new { |h, k| h[k] = {} }.compare_by_identity
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -10,6 +10,8 @@ module OpenTelemetry
|
|
|
10
10
|
module Aggregation
|
|
11
11
|
# Contains the implementation of the Sum aggregation
|
|
12
12
|
class Sum
|
|
13
|
+
include StreamScopedStorage
|
|
14
|
+
|
|
13
15
|
OVERFLOW_ATTRIBUTE_SET = { 'otel.metric.overflow' => true }.freeze
|
|
14
16
|
attr_reader :exemplar_reservoir
|
|
15
17
|
|
|
@@ -24,18 +26,20 @@ module OpenTelemetry
|
|
|
24
26
|
@aggregation_temporality = AggregationTemporality.determine_temporality(aggregation_temporality: aggregation_temporality, instrument_kind: instrument_kind, default: :cumulative)
|
|
25
27
|
@monotonic = monotonic
|
|
26
28
|
@exemplar_reservoir = exemplar_reservoir || DEFAULT_RESERVOIR
|
|
27
|
-
@exemplar_reservoir_storage =
|
|
29
|
+
@exemplar_reservoir_storage = new_stream_storage
|
|
28
30
|
end
|
|
29
31
|
|
|
30
32
|
# Returns the current sum data points, clearing them for delta temporality.
|
|
31
33
|
def collect(start_time, end_time, data_points)
|
|
34
|
+
stream_exemplar_reservoir_storage = @exemplar_reservoir_storage[data_points]
|
|
35
|
+
|
|
32
36
|
if @aggregation_temporality.delta?
|
|
33
37
|
# Set timestamps and 'move' data point values to result.
|
|
34
38
|
ndps = data_points.values.map! do |ndp|
|
|
35
39
|
ndp.start_time_unix_nano = start_time
|
|
36
40
|
ndp.time_unix_nano = end_time
|
|
37
|
-
reservoir =
|
|
38
|
-
ndp.exemplars = reservoir&.collect(attributes: ndp.attributes, aggregation_temporality: @aggregation_temporality)
|
|
41
|
+
reservoir = stream_exemplar_reservoir_storage[ndp.attributes]
|
|
42
|
+
ndp.exemplars = reservoir&.collect(attributes: ndp.attributes, aggregation_temporality: @aggregation_temporality.temporality)
|
|
39
43
|
ndp
|
|
40
44
|
end
|
|
41
45
|
data_points.clear
|
|
@@ -45,8 +49,8 @@ module OpenTelemetry
|
|
|
45
49
|
data_points.values.map! do |ndp|
|
|
46
50
|
ndp.start_time_unix_nano ||= start_time # Start time of a data point is from the first observation.
|
|
47
51
|
ndp.time_unix_nano = end_time
|
|
48
|
-
reservoir =
|
|
49
|
-
ndp.exemplars = reservoir&.collect(attributes: ndp.attributes, aggregation_temporality: @aggregation_temporality)
|
|
52
|
+
reservoir = stream_exemplar_reservoir_storage[ndp.attributes]
|
|
53
|
+
ndp.exemplars = reservoir&.collect(attributes: ndp.attributes, aggregation_temporality: @aggregation_temporality.temporality)
|
|
50
54
|
ndp.dup
|
|
51
55
|
end
|
|
52
56
|
end
|
|
@@ -65,7 +69,7 @@ module OpenTelemetry
|
|
|
65
69
|
create_new_data_point(attributes, data_points)
|
|
66
70
|
end
|
|
67
71
|
|
|
68
|
-
update_number_data_point(ndp, increment, exemplar_offer: exemplar_offer)
|
|
72
|
+
update_number_data_point(ndp, increment, data_points, exemplar_offer: exemplar_offer)
|
|
69
73
|
nil
|
|
70
74
|
end
|
|
71
75
|
|
|
@@ -92,17 +96,17 @@ module OpenTelemetry
|
|
|
92
96
|
)
|
|
93
97
|
end
|
|
94
98
|
|
|
95
|
-
def update_number_data_point(ndp, increment, exemplar_offer: false)
|
|
96
|
-
reservior_update(ndp.attributes, increment, exemplar_offer)
|
|
99
|
+
def update_number_data_point(ndp, increment, stream_key, exemplar_offer: false)
|
|
100
|
+
reservior_update(ndp.attributes, increment, exemplar_offer, stream_key)
|
|
97
101
|
ndp.value += increment
|
|
98
102
|
end
|
|
99
103
|
|
|
100
|
-
def reservior_update(attributes, increment, exemplar_offer)
|
|
101
|
-
reservoir = @exemplar_reservoir_storage[attributes]
|
|
104
|
+
def reservior_update(attributes, increment, exemplar_offer, stream_key)
|
|
105
|
+
reservoir = @exemplar_reservoir_storage[stream_key][attributes]
|
|
102
106
|
unless reservoir
|
|
103
107
|
reservoir = @exemplar_reservoir.dup
|
|
104
108
|
reservoir.reset
|
|
105
|
-
@exemplar_reservoir_storage[attributes] = reservoir
|
|
109
|
+
@exemplar_reservoir_storage[stream_key][attributes] = reservoir
|
|
106
110
|
end
|
|
107
111
|
|
|
108
112
|
return unless exemplar_offer
|
|
@@ -16,6 +16,7 @@ module OpenTelemetry
|
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
require 'opentelemetry/sdk/metrics/aggregation/aggregation_temporality'
|
|
19
|
+
require 'opentelemetry/sdk/metrics/aggregation/stream_scoped_storage'
|
|
19
20
|
require 'opentelemetry/sdk/metrics/aggregation/number_data_point'
|
|
20
21
|
require 'opentelemetry/sdk/metrics/aggregation/histogram_data_point'
|
|
21
22
|
require 'opentelemetry/sdk/metrics/aggregation/explicit_bucket_histogram'
|
|
@@ -12,12 +12,27 @@ module OpenTelemetry
|
|
|
12
12
|
# It is not required to subclass this class to provide an implementation
|
|
13
13
|
# of MetricReader, provided the interface is satisfied.
|
|
14
14
|
class MetricReader
|
|
15
|
+
# Raised when a reader is registered with a second MeterProvider.
|
|
16
|
+
SINGLE_PROVIDER_ERROR = 'MetricReader cannot be registered with more than one MeterProvider'
|
|
17
|
+
|
|
18
|
+
private_constant :SINGLE_PROVIDER_ERROR
|
|
19
|
+
|
|
15
20
|
attr_reader :metric_store
|
|
16
21
|
|
|
17
22
|
def initialize(aggregation_cardinality_limit: nil)
|
|
23
|
+
@mutex = Mutex.new
|
|
18
24
|
@metric_store = OpenTelemetry::SDK::Metrics::State::MetricStore.new(cardinality_limit: aggregation_cardinality_limit)
|
|
19
25
|
end
|
|
20
26
|
|
|
27
|
+
# Registers this reader with a MeterProvider.
|
|
28
|
+
def register_meter_provider(meter_provider)
|
|
29
|
+
@mutex.synchronize do
|
|
30
|
+
raise ArgumentError, SINGLE_PROVIDER_ERROR if @meter_provider && !@meter_provider.equal?(meter_provider)
|
|
31
|
+
|
|
32
|
+
@meter_provider = meter_provider
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
21
36
|
# Collects and returns the current metrics from the metric store.
|
|
22
37
|
def collect
|
|
23
38
|
@metric_store.collect
|
|
@@ -53,7 +53,9 @@ module OpenTelemetry
|
|
|
53
53
|
# The API MUST support creation of asynchronous instruments by passing zero or more callback functions
|
|
54
54
|
# to be permanently registered to the newly created instrument.
|
|
55
55
|
def init_callback(callback)
|
|
56
|
-
if callback.
|
|
56
|
+
if callback.nil?
|
|
57
|
+
nil # zero callbacks permanently registered at creation is permitted
|
|
58
|
+
elsif callback.instance_of?(Proc)
|
|
57
59
|
@callbacks << callback
|
|
58
60
|
elsif callback.instance_of?(Array)
|
|
59
61
|
callback.each { |cb| @callbacks << cb if cb.instance_of?(Proc) }
|
|
@@ -63,12 +63,14 @@ module OpenTelemetry
|
|
|
63
63
|
end
|
|
64
64
|
|
|
65
65
|
# Validates the given instrument options and creates the instrument of the given kind.
|
|
66
|
-
|
|
66
|
+
# TODO: advisory parameters are accepted but not yet applied to stream configuration.
|
|
67
|
+
def create_instrument(kind, name, unit, description, callback, exemplar_filter, exemplar_reservoir, advisory)
|
|
67
68
|
raise InstrumentNameError if name.nil?
|
|
68
69
|
raise InstrumentNameError if name.empty?
|
|
69
70
|
raise InstrumentNameError unless NAME_REGEX.match?(name)
|
|
70
|
-
|
|
71
|
-
|
|
71
|
+
|
|
72
|
+
unit ||= ''
|
|
73
|
+
description ||= ''
|
|
72
74
|
|
|
73
75
|
super do
|
|
74
76
|
case kind
|
|
@@ -82,12 +84,6 @@ module OpenTelemetry
|
|
|
82
84
|
end
|
|
83
85
|
end
|
|
84
86
|
end
|
|
85
|
-
|
|
86
|
-
# Returns whether string is valid UTF-8 with no 4-byte (utf8mb4) characters.
|
|
87
|
-
def utf8mb3_encoding?(string)
|
|
88
|
-
string.force_encoding('UTF-8').valid_encoding? &&
|
|
89
|
-
string.each_char { |c| return false if c.bytesize >= 4 }
|
|
90
|
-
end
|
|
91
87
|
end
|
|
92
88
|
end
|
|
93
89
|
end
|
|
@@ -13,6 +13,11 @@ module OpenTelemetry
|
|
|
13
13
|
# rubocop:disable-next Metrics/ClassLength
|
|
14
14
|
class MeterProvider < OpenTelemetry::Metrics::MeterProvider
|
|
15
15
|
EMPTY_ATTRIBUTES = {}.freeze
|
|
16
|
+
READER_OWNERS = ObjectSpace::WeakMap.new
|
|
17
|
+
READER_OWNERS_MUTEX = Mutex.new
|
|
18
|
+
READER_OWNERSHIP_ERROR = 'MetricReader cannot be registered with more than one MeterProvider'
|
|
19
|
+
|
|
20
|
+
private_constant :READER_OWNERS, :READER_OWNERS_MUTEX, :READER_OWNERSHIP_ERROR
|
|
16
21
|
|
|
17
22
|
Key = Struct.new(:name, :version, :attributes)
|
|
18
23
|
private_constant(:Key)
|
|
@@ -118,6 +123,7 @@ module OpenTelemetry
|
|
|
118
123
|
if @stopped
|
|
119
124
|
OpenTelemetry.logger.warn('calling MetricProvider#add_metric_reader after shutdown.')
|
|
120
125
|
else
|
|
126
|
+
register_metric_reader(metric_reader)
|
|
121
127
|
@metric_readers.push(metric_reader)
|
|
122
128
|
@meter_registry.each_value { |meter| meter.add_metric_reader(metric_reader) }
|
|
123
129
|
end
|
|
@@ -126,6 +132,18 @@ module OpenTelemetry
|
|
|
126
132
|
end
|
|
127
133
|
end
|
|
128
134
|
|
|
135
|
+
def register_metric_reader(metric_reader)
|
|
136
|
+
READER_OWNERS_MUTEX.synchronize do
|
|
137
|
+
owner = READER_OWNERS[metric_reader]
|
|
138
|
+
raise ArgumentError, READER_OWNERSHIP_ERROR if owner && !owner.equal?(self)
|
|
139
|
+
|
|
140
|
+
READER_OWNERS[metric_reader] = self
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
metric_reader.register_meter_provider(self) if metric_reader.respond_to?(:register_meter_provider)
|
|
144
|
+
end
|
|
145
|
+
private :register_metric_reader
|
|
146
|
+
|
|
129
147
|
# @api private
|
|
130
148
|
def register_synchronous_instrument(instrument)
|
|
131
149
|
@mutex.synchronize do
|
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.
|
|
4
|
+
version: 0.19.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- OpenTelemetry Authors
|
|
@@ -78,6 +78,7 @@ files:
|
|
|
78
78
|
- lib/opentelemetry/sdk/metrics/aggregation/histogram_data_point.rb
|
|
79
79
|
- lib/opentelemetry/sdk/metrics/aggregation/last_value.rb
|
|
80
80
|
- lib/opentelemetry/sdk/metrics/aggregation/number_data_point.rb
|
|
81
|
+
- lib/opentelemetry/sdk/metrics/aggregation/stream_scoped_storage.rb
|
|
81
82
|
- lib/opentelemetry/sdk/metrics/aggregation/sum.rb
|
|
82
83
|
- lib/opentelemetry/sdk/metrics/configurator_patch.rb
|
|
83
84
|
- lib/opentelemetry/sdk/metrics/exemplar.rb
|
|
@@ -121,10 +122,10 @@ homepage: https://github.com/open-telemetry/opentelemetry-ruby
|
|
|
121
122
|
licenses:
|
|
122
123
|
- Apache-2.0
|
|
123
124
|
metadata:
|
|
124
|
-
changelog_uri: https://rubydoc.info/gems/opentelemetry-metrics-sdk/0.
|
|
125
|
-
source_code_uri: https://github.com/open-telemetry/opentelemetry-ruby/tree/opentelemetry-metrics-sdk/v0.
|
|
125
|
+
changelog_uri: https://rubydoc.info/gems/opentelemetry-metrics-sdk/0.19.0/file/CHANGELOG.md
|
|
126
|
+
source_code_uri: https://github.com/open-telemetry/opentelemetry-ruby/tree/opentelemetry-metrics-sdk/v0.19.0/metrics_sdk
|
|
126
127
|
bug_tracker_uri: https://github.com/open-telemetry/opentelemetry-ruby/issues
|
|
127
|
-
documentation_uri: https://rubydoc.info/gems/opentelemetry-metrics-sdk/0.
|
|
128
|
+
documentation_uri: https://rubydoc.info/gems/opentelemetry-metrics-sdk/0.19.0
|
|
128
129
|
rdoc_options: []
|
|
129
130
|
require_paths:
|
|
130
131
|
- lib
|
|
@@ -139,7 +140,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
139
140
|
- !ruby/object:Gem::Version
|
|
140
141
|
version: '0'
|
|
141
142
|
requirements: []
|
|
142
|
-
rubygems_version: 4.0.
|
|
143
|
+
rubygems_version: 4.0.20
|
|
143
144
|
specification_version: 4
|
|
144
145
|
summary: A stats collection and distributed tracing framework
|
|
145
146
|
test_files: []
|