opentelemetry-metrics-sdk 0.12.0 → 0.13.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 +5 -0
- data/README.md +237 -28
- data/lib/opentelemetry/sdk/metrics/aggregation/drop.rb +10 -2
- data/lib/opentelemetry/sdk/metrics/aggregation/explicit_bucket_histogram.rb +25 -2
- data/lib/opentelemetry/sdk/metrics/aggregation/exponential_bucket_histogram.rb +34 -7
- data/lib/opentelemetry/sdk/metrics/aggregation/last_value.rb +28 -1
- data/lib/opentelemetry/sdk/metrics/aggregation/sum.rb +31 -2
- data/lib/opentelemetry/sdk/metrics/exemplar/aligned_histogram_bucket_exemplar_reservoir.rb +72 -0
- data/lib/opentelemetry/sdk/metrics/exemplar/always_off_exemplar_filter.rb +21 -0
- data/lib/opentelemetry/sdk/metrics/exemplar/always_on_exemplar_filter.rb +20 -0
- data/lib/opentelemetry/sdk/metrics/exemplar/exemplar.rb +23 -0
- data/lib/opentelemetry/sdk/metrics/exemplar/exemplar_bucket.rb +75 -0
- data/lib/opentelemetry/sdk/metrics/exemplar/exemplar_filter.rb +25 -0
- data/lib/opentelemetry/sdk/metrics/exemplar/exemplar_reservoir.rb +39 -0
- data/lib/opentelemetry/sdk/metrics/exemplar/noop_exemplar_reservoir.rb +22 -0
- data/lib/opentelemetry/sdk/metrics/exemplar/simple_fixed_size_exemplar_reservoir.rb +59 -0
- data/lib/opentelemetry/sdk/metrics/exemplar/trace_based_exemplar_filter.rb +27 -0
- data/lib/opentelemetry/sdk/metrics/exemplar.rb +32 -0
- data/lib/opentelemetry/sdk/metrics/instrument/asynchronous_instrument.rb +18 -2
- data/lib/opentelemetry/sdk/metrics/instrument/counter.rb +1 -1
- data/lib/opentelemetry/sdk/metrics/instrument/gauge.rb +1 -1
- data/lib/opentelemetry/sdk/metrics/instrument/histogram.rb +3 -1
- data/lib/opentelemetry/sdk/metrics/instrument/observable_counter.rb +1 -1
- data/lib/opentelemetry/sdk/metrics/instrument/observable_gauge.rb +1 -1
- data/lib/opentelemetry/sdk/metrics/instrument/observable_up_down_counter.rb +1 -1
- data/lib/opentelemetry/sdk/metrics/instrument/synchronous_instrument.rb +6 -2
- data/lib/opentelemetry/sdk/metrics/instrument/up_down_counter.rb +1 -1
- data/lib/opentelemetry/sdk/metrics/meter.rb +8 -8
- data/lib/opentelemetry/sdk/metrics/meter_provider.rb +33 -1
- data/lib/opentelemetry/sdk/metrics/state/asynchronous_metric_stream.rb +12 -4
- data/lib/opentelemetry/sdk/metrics/state/metric_stream.rb +21 -4
- data/lib/opentelemetry/sdk/metrics/version.rb +1 -1
- data/lib/opentelemetry/sdk/metrics.rb +1 -0
- metadata +19 -25
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ab86f13e4c2f144a3c7e0016427f84cd3b441e73ea7a1763b87ce0d65cdfe09a
|
|
4
|
+
data.tar.gz: 0a6108e36f68f835015d2788dfda936fe32dd9dece37c89f9544cc2954598a89
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 63c87d0216721b418f168d92e419006c3b552a0eb1dd2de9b804f773a7928c14505eac77691ef92782ab69a7d457c916cb162a89098eee9fa382f8ac3e27526d
|
|
7
|
+
data.tar.gz: fe4b4ad19c93bdb896d66d54c65a3500eb7e1237af6d1c992b64129ad050fe93351bb7a6e1812035db123fc81795ccb29be479c48593c0f2f45402cbac60d490
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Release History: opentelemetry-metrics-sdk
|
|
2
2
|
|
|
3
|
+
### v0.13.0 / 2026-04-07
|
|
4
|
+
|
|
5
|
+
* ADDED: Min Ruby Version 3.3 (#2070)
|
|
6
|
+
* ADDED: Add basic support for metrics exemplar (#1609)
|
|
7
|
+
|
|
3
8
|
### v0.12.0 / 2026-02-11
|
|
4
9
|
|
|
5
10
|
* BREAKING CHANGE: Fix the issue of mixed scale with multiple attributes
|
data/README.md
CHANGED
|
@@ -12,28 +12,36 @@ OpenTelemetry provides a single set of APIs, libraries, agents, and collector se
|
|
|
12
12
|
|
|
13
13
|
Metrics is one of the core signals in OpenTelemetry. This package allows you to emit OpenTelemetry metrics using Ruby. It leverages an alpha implementation of the OpenTelemetry Metrics API. At the current stage, things may break and APIs may change. Use this tool with caution.
|
|
14
14
|
|
|
15
|
-
This gem does not have a full implementation of the Metrics SDK specification.
|
|
15
|
+
This gem does not yet have a full implementation of the Metrics SDK specification. Work is in progress.
|
|
16
16
|
|
|
17
17
|
At this time, you should be able to:
|
|
18
18
|
|
|
19
|
-
* Create synchronous:
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
19
|
+
* Create all synchronous instruments:
|
|
20
|
+
* `Counter`
|
|
21
|
+
* `UpDownCounter`
|
|
22
|
+
* `Histogram`
|
|
23
|
+
* `Gauge`
|
|
24
|
+
* Create all asynchronous (observable) instruments:
|
|
25
|
+
* `ObservableCounter`
|
|
26
|
+
* `ObservableGauge`
|
|
27
|
+
* `ObservableUpDownCounter`
|
|
28
|
+
* Use all aggregation types:
|
|
29
|
+
* `ExplicitBucketHistogram` (default for histograms)
|
|
30
|
+
* `ExponentialBucketHistogram`
|
|
31
|
+
* `Sum` (default for counters and up-down counters)
|
|
32
|
+
* `LastValue` (default for gauges)
|
|
33
|
+
* `Drop`
|
|
34
|
+
* Configure aggregation temporality: delta, cumulative, or low-memory
|
|
35
|
+
* Customize metric collection with Views (filter by name, type, unit, aggregation, attribute keys)
|
|
36
|
+
* Export metrics using pull-based exporters:
|
|
37
|
+
* `ConsoleMetricPullExporter`
|
|
38
|
+
* `InMemoryMetricPullExporter` (for testing)
|
|
39
|
+
* Export metrics on a schedule using `PeriodicMetricReader` with any compatible push exporter (e.g. OTLP via `opentelemetry-exporter-otlp-metrics`)
|
|
29
40
|
|
|
30
41
|
We do not yet have support for:
|
|
31
42
|
|
|
32
|
-
* Asynchronous instruments
|
|
33
|
-
* Cumulative aggregation temporality
|
|
34
|
-
* Metrics Views
|
|
35
43
|
* Metrics Exemplars
|
|
36
|
-
*
|
|
44
|
+
* `schema_url` in view configuration
|
|
37
45
|
|
|
38
46
|
These lists are incomplete and are intended to give a broad description of what's available.
|
|
39
47
|
|
|
@@ -56,31 +64,232 @@ Then, configure the SDK according to your desired handling of telemetry data, an
|
|
|
56
64
|
require 'opentelemetry/sdk'
|
|
57
65
|
require 'opentelemetry-metrics-sdk'
|
|
58
66
|
|
|
59
|
-
#
|
|
67
|
+
# Disable automatic exporter configuration so we can set one manually.
|
|
68
|
+
ENV['OTEL_METRICS_EXPORTER'] = 'none'
|
|
69
|
+
|
|
70
|
+
OpenTelemetry::SDK.configure
|
|
71
|
+
|
|
72
|
+
# Create an exporter and register it with the meter provider.
|
|
73
|
+
console_exporter = OpenTelemetry::SDK::Metrics::Export::ConsoleMetricPullExporter.new
|
|
74
|
+
OpenTelemetry.meter_provider.add_metric_reader(console_exporter)
|
|
75
|
+
|
|
76
|
+
# Create a meter and instrument.
|
|
77
|
+
meter = OpenTelemetry.meter_provider.meter('my_app')
|
|
78
|
+
histogram = meter.create_histogram('http.request.duration', unit: 'ms', description: 'HTTP request duration')
|
|
79
|
+
|
|
80
|
+
# Record a measurement.
|
|
81
|
+
histogram.record(200, attributes: { 'http.method' => 'GET', 'http.status_code' => '200' })
|
|
82
|
+
|
|
83
|
+
# Flush metrics to the exporter.
|
|
84
|
+
OpenTelemetry.meter_provider.metric_readers.each(&:pull)
|
|
85
|
+
|
|
86
|
+
OpenTelemetry.meter_provider.shutdown
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### All synchronous instruments
|
|
90
|
+
|
|
91
|
+
```ruby
|
|
92
|
+
meter = OpenTelemetry.meter_provider.meter('my_app')
|
|
93
|
+
|
|
94
|
+
# Counter — monotonically increasing value
|
|
95
|
+
counter = meter.create_counter('requests.total', unit: '1', description: 'Total requests')
|
|
96
|
+
counter.add(1, attributes: { 'service' => 'web' })
|
|
97
|
+
|
|
98
|
+
# UpDownCounter — value that can increase or decrease
|
|
99
|
+
queue_depth = meter.create_up_down_counter('queue.depth', unit: '1', description: 'Items in queue')
|
|
100
|
+
queue_depth.add(5)
|
|
101
|
+
queue_depth.add(-3)
|
|
102
|
+
|
|
103
|
+
# Histogram — distribution of measurements
|
|
104
|
+
duration = meter.create_histogram('db.query.duration', unit: 'ms', description: 'Database query duration')
|
|
105
|
+
duration.record(42, attributes: { 'db.operation' => 'SELECT' })
|
|
106
|
+
|
|
107
|
+
# Gauge — current value at observation time
|
|
108
|
+
temperature = meter.create_gauge('system.temperature', unit: 'cel', description: 'Current temperature')
|
|
109
|
+
temperature.record(23.5, attributes: { 'sensor' => 'cpu' })
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Asynchronous (observable) instruments
|
|
113
|
+
|
|
114
|
+
Asynchronous instruments collect measurements via a callback that is invoked when the metric reader collects data.
|
|
115
|
+
|
|
116
|
+
```ruby
|
|
117
|
+
require 'opentelemetry/sdk'
|
|
118
|
+
require 'opentelemetry-metrics-sdk'
|
|
119
|
+
|
|
120
|
+
ENV['OTEL_METRICS_EXPORTER'] = 'none'
|
|
60
121
|
OpenTelemetry::SDK.configure
|
|
61
122
|
|
|
62
|
-
|
|
63
|
-
|
|
123
|
+
console_exporter = OpenTelemetry::SDK::Metrics::Export::ConsoleMetricPullExporter.new
|
|
124
|
+
OpenTelemetry.meter_provider.add_metric_reader(console_exporter)
|
|
64
125
|
|
|
65
|
-
|
|
66
|
-
OpenTelemetry.meter_provider.add_metric_reader(console_metric_exporter)
|
|
126
|
+
meter = OpenTelemetry.meter_provider.meter('my_app')
|
|
67
127
|
|
|
68
|
-
#
|
|
69
|
-
|
|
128
|
+
# ObservableCounter — monotonically increasing, measured on demand
|
|
129
|
+
cpu_callback = proc { `ps -p #{Process.pid} -o %cpu=`.strip.to_f }
|
|
130
|
+
cpu_counter = meter.create_observable_counter('process.cpu.usage', callback: cpu_callback, unit: 'ms')
|
|
70
131
|
|
|
71
|
-
#
|
|
72
|
-
|
|
132
|
+
# ObservableGauge — current value, measured on demand
|
|
133
|
+
mem_callback = proc { `ps -p #{Process.pid} -o %mem=`.strip.to_f }
|
|
134
|
+
mem_gauge = meter.create_observable_gauge('process.memory.usage', callback: mem_callback, unit: 'percent')
|
|
73
135
|
|
|
74
|
-
#
|
|
75
|
-
|
|
136
|
+
# ObservableUpDownCounter — can increase or decrease, measured on demand
|
|
137
|
+
queue_callback = proc { JobQueue.current_depth }
|
|
138
|
+
queue_counter = meter.create_observable_up_down_counter('jobs.queue.depth', callback: queue_callback, unit: '1')
|
|
139
|
+
|
|
140
|
+
# Trigger callbacks and export
|
|
141
|
+
cpu_counter.observe
|
|
142
|
+
mem_gauge.observe
|
|
143
|
+
queue_counter.observe
|
|
76
144
|
|
|
77
|
-
# Send the recorded metrics to the metric readers.
|
|
78
145
|
OpenTelemetry.meter_provider.metric_readers.each(&:pull)
|
|
146
|
+
OpenTelemetry.meter_provider.shutdown
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Views
|
|
150
|
+
|
|
151
|
+
Views let you customize how metrics are collected and exported — changing the aggregation, filtering attribute keys, or dropping instruments entirely.
|
|
79
152
|
|
|
80
|
-
|
|
153
|
+
#### Change aggregation for a specific instrument
|
|
154
|
+
|
|
155
|
+
```ruby
|
|
156
|
+
require 'opentelemetry/sdk'
|
|
157
|
+
require 'opentelemetry-metrics-sdk'
|
|
158
|
+
|
|
159
|
+
ENV['OTEL_METRICS_EXPORTER'] = 'none'
|
|
160
|
+
OpenTelemetry::SDK.configure
|
|
161
|
+
|
|
162
|
+
console_exporter = OpenTelemetry::SDK::Metrics::Export::ConsoleMetricPullExporter.new
|
|
163
|
+
OpenTelemetry.meter_provider.add_metric_reader(console_exporter)
|
|
164
|
+
|
|
165
|
+
# Use exponential histogram aggregation for any histogram whose name contains "exponential".
|
|
166
|
+
# The view name supports * (match any characters) and ? (match one character) wildcards.
|
|
167
|
+
OpenTelemetry.meter_provider.add_view(
|
|
168
|
+
'*exponential*',
|
|
169
|
+
aggregation: OpenTelemetry::SDK::Metrics::Aggregation::ExponentialBucketHistogram.new(
|
|
170
|
+
aggregation_temporality: :cumulative,
|
|
171
|
+
max_scale: 20
|
|
172
|
+
),
|
|
173
|
+
type: :histogram,
|
|
174
|
+
unit: 'ms'
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
meter = OpenTelemetry.meter_provider.meter('my_app')
|
|
178
|
+
hist = meter.create_histogram('http.exponential.latency', unit: 'ms', description: 'Latency distribution')
|
|
179
|
+
(1..10).each { |i| hist.record(i ** 2, attributes: { 'env' => 'prod' }) }
|
|
180
|
+
|
|
181
|
+
OpenTelemetry.meter_provider.metric_readers.each(&:pull)
|
|
81
182
|
OpenTelemetry.meter_provider.shutdown
|
|
82
183
|
```
|
|
83
184
|
|
|
185
|
+
#### Drop an instrument
|
|
186
|
+
|
|
187
|
+
```ruby
|
|
188
|
+
# Drop all metrics from a specific meter — useful for suppressing noisy or low-value instrumentation.
|
|
189
|
+
OpenTelemetry.meter_provider.add_view(
|
|
190
|
+
'*',
|
|
191
|
+
aggregation: OpenTelemetry::SDK::Metrics::Aggregation::Drop.new,
|
|
192
|
+
meter_name: 'noisy_library'
|
|
193
|
+
)
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
#### Restrict which attribute keys are retained
|
|
197
|
+
|
|
198
|
+
```ruby
|
|
199
|
+
# Only keep the 'http.method' and 'http.status_code' attributes; all others are dropped.
|
|
200
|
+
OpenTelemetry.meter_provider.add_view(
|
|
201
|
+
'http.request.duration',
|
|
202
|
+
attribute_keys: { 'http.method' => nil, 'http.status_code' => nil }
|
|
203
|
+
)
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
#### Full view options reference
|
|
207
|
+
|
|
208
|
+
```ruby
|
|
209
|
+
OpenTelemetry.meter_provider.add_view(
|
|
210
|
+
'instrument_name_pattern', # supports * and ? wildcards; matches against instrument name
|
|
211
|
+
aggregation: OpenTelemetry::SDK::Metrics::Aggregation::ExplicitBucketHistogram.new,
|
|
212
|
+
type: :histogram, # instrument kind: :counter, :up_down_counter, :histogram,
|
|
213
|
+
# :gauge, :observable_counter, :observable_gauge,
|
|
214
|
+
# :observable_up_down_counter
|
|
215
|
+
unit: 'ms', # matches instruments with this unit
|
|
216
|
+
meter_name: 'my_meter', # matches instruments from this meter
|
|
217
|
+
meter_version: '1.0', # matches instruments from this meter version
|
|
218
|
+
attribute_keys: { 'env' => nil, 'region' => nil } # allowlist of attribute keys to retain
|
|
219
|
+
)
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Aggregation temporality
|
|
223
|
+
|
|
224
|
+
Aggregation temporality controls whether exported values represent measurements since the last export (delta) or since the process started (cumulative). Configure it globally via the environment variable or per-aggregation:
|
|
225
|
+
|
|
226
|
+
```ruby
|
|
227
|
+
# Via environment variable (applies to all OTLP exports):
|
|
228
|
+
# OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE=cumulative (or delta, or lowmemory)
|
|
229
|
+
|
|
230
|
+
# Per-aggregation:
|
|
231
|
+
sum_agg = OpenTelemetry::SDK::Metrics::Aggregation::Sum.new(aggregation_temporality: :delta)
|
|
232
|
+
hist_agg = OpenTelemetry::SDK::Metrics::Aggregation::ExplicitBucketHistogram.new(
|
|
233
|
+
aggregation_temporality: :cumulative,
|
|
234
|
+
boundaries: [0, 10, 50, 100, 500, 1000]
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
OpenTelemetry.meter_provider.add_view('my_counter', aggregation: sum_agg, type: :counter)
|
|
238
|
+
OpenTelemetry.meter_provider.add_view('my_histogram', aggregation: hist_agg, type: :histogram)
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
| Temporality preference | Counter | Observable Counter | Histogram | UpDownCounter | Observable UpDownCounter |
|
|
242
|
+
| --- | --- | --- | --- | --- | --- |
|
|
243
|
+
| `cumulative` | Cumulative | Cumulative | Cumulative | Cumulative | Cumulative |
|
|
244
|
+
| `delta` | Delta | Delta | Delta | Cumulative | Cumulative |
|
|
245
|
+
| `lowmemory` | Delta | Cumulative | Delta | Cumulative | Cumulative |
|
|
246
|
+
|
|
247
|
+
### Periodic exporting with PeriodicMetricReader
|
|
248
|
+
|
|
249
|
+
Use `PeriodicMetricReader` to wrap any push exporter (such as the OTLP exporter) and automatically export on a schedule:
|
|
250
|
+
|
|
251
|
+
```ruby
|
|
252
|
+
require 'opentelemetry/sdk'
|
|
253
|
+
require 'opentelemetry-metrics-sdk'
|
|
254
|
+
require 'opentelemetry-exporter-otlp-metrics'
|
|
255
|
+
|
|
256
|
+
ENV['OTEL_METRICS_EXPORTER'] = 'none'
|
|
257
|
+
OpenTelemetry::SDK.configure
|
|
258
|
+
|
|
259
|
+
otlp_exporter = OpenTelemetry::Exporter::OTLP::Metrics::MetricsExporter.new
|
|
260
|
+
periodic_reader = OpenTelemetry::SDK::Metrics::Export::PeriodicMetricReader.new(
|
|
261
|
+
export_interval_millis: 5_000, # default: OTEL_METRIC_EXPORT_INTERVAL (60_000 ms)
|
|
262
|
+
export_timeout_millis: 1_000, # default: OTEL_METRIC_EXPORT_TIMEOUT (30_000 ms)
|
|
263
|
+
exporter: otlp_exporter
|
|
264
|
+
)
|
|
265
|
+
OpenTelemetry.meter_provider.add_metric_reader(periodic_reader)
|
|
266
|
+
|
|
267
|
+
meter = OpenTelemetry.meter_provider.meter('my_app')
|
|
268
|
+
counter = meter.create_counter('requests.total', unit: '1')
|
|
269
|
+
counter.add(1, attributes: { 'service' => 'web' })
|
|
270
|
+
|
|
271
|
+
OpenTelemetry.meter_provider.shutdown
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Using InMemoryMetricPullExporter for testing
|
|
275
|
+
|
|
276
|
+
```ruby
|
|
277
|
+
require 'opentelemetry-metrics-sdk'
|
|
278
|
+
|
|
279
|
+
exporter = OpenTelemetry::SDK::Metrics::Export::InMemoryMetricPullExporter.new
|
|
280
|
+
OpenTelemetry.meter_provider.add_metric_reader(exporter)
|
|
281
|
+
|
|
282
|
+
meter = OpenTelemetry.meter_provider.meter('test_meter')
|
|
283
|
+
counter = meter.create_counter('test.counter', unit: '1')
|
|
284
|
+
counter.add(5, attributes: { 'env' => 'test' })
|
|
285
|
+
|
|
286
|
+
exporter.pull
|
|
287
|
+
snapshots = exporter.metric_snapshots # Array of MetricData structs
|
|
288
|
+
# => [#<struct name="test.counter", data_points=[...]>]
|
|
289
|
+
|
|
290
|
+
exporter.reset # clear accumulated snapshots between test cases
|
|
291
|
+
```
|
|
292
|
+
|
|
84
293
|
For additional examples, see the [examples on github][examples-github].
|
|
85
294
|
|
|
86
295
|
## How can I get involved?
|
|
@@ -10,17 +10,25 @@ module OpenTelemetry
|
|
|
10
10
|
module Aggregation
|
|
11
11
|
# Contains the implementation of the Drop aggregation
|
|
12
12
|
class Drop
|
|
13
|
+
# Use noop reservoir since this is a no-op aggregation
|
|
14
|
+
DEFAULT_RESERVOIR = Metrics::Exemplar::NoopExemplarReservoir.new
|
|
15
|
+
private_constant :DEFAULT_RESERVOIR
|
|
16
|
+
|
|
17
|
+
def initialize(exemplar_reservoir: nil)
|
|
18
|
+
@exemplar_reservoir = DEFAULT_RESERVOIR
|
|
19
|
+
end
|
|
20
|
+
|
|
13
21
|
def collect(start_time, end_time, data_points)
|
|
14
22
|
data_points.values.map!(&:dup)
|
|
15
23
|
end
|
|
16
24
|
|
|
17
|
-
def update(increment, attributes, data_points)
|
|
25
|
+
def update(increment, attributes, data_points, exemplar_offer: false)
|
|
18
26
|
data_points[attributes] = NumberDataPoint.new(
|
|
19
27
|
{},
|
|
20
28
|
0,
|
|
21
29
|
0,
|
|
22
30
|
0,
|
|
23
|
-
|
|
31
|
+
[]
|
|
24
32
|
)
|
|
25
33
|
nil
|
|
26
34
|
end
|
|
@@ -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
|
|
14
|
+
attr_reader :exemplar_reservoir
|
|
15
|
+
|
|
14
16
|
DEFAULT_BOUNDARIES = [0, 5, 10, 25, 50, 75, 100, 250, 500, 1000].freeze
|
|
15
17
|
private_constant :DEFAULT_BOUNDARIES
|
|
16
18
|
|
|
@@ -21,11 +23,14 @@ module OpenTelemetry
|
|
|
21
23
|
def initialize(
|
|
22
24
|
aggregation_temporality: ENV.fetch('OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE', :cumulative),
|
|
23
25
|
boundaries: DEFAULT_BOUNDARIES,
|
|
24
|
-
record_min_max: true
|
|
26
|
+
record_min_max: true,
|
|
27
|
+
exemplar_reservoir: nil
|
|
25
28
|
)
|
|
26
29
|
@aggregation_temporality = AggregationTemporality.determine_temporality(aggregation_temporality: aggregation_temporality, default: :cumulative)
|
|
27
30
|
@boundaries = boundaries && !boundaries.empty? ? boundaries.sort : nil
|
|
28
31
|
@record_min_max = record_min_max
|
|
32
|
+
@exemplar_reservoir = exemplar_reservoir || Metrics::Exemplar::AlignedHistogramBucketExemplarReservoir.new(boundaries: @boundaries)
|
|
33
|
+
@exemplar_reservoir_storage = {}
|
|
29
34
|
end
|
|
30
35
|
|
|
31
36
|
def collect(start_time, end_time, data_points)
|
|
@@ -34,6 +39,8 @@ module OpenTelemetry
|
|
|
34
39
|
hdps = data_points.values.map! do |hdp|
|
|
35
40
|
hdp.start_time_unix_nano = start_time
|
|
36
41
|
hdp.time_unix_nano = end_time
|
|
42
|
+
reservoir = @exemplar_reservoir_storage[hdp.attributes]
|
|
43
|
+
hdp.exemplars = reservoir&.collect(attributes: hdp.attributes, aggregation_temporality: @aggregation_temporality)
|
|
37
44
|
hdp
|
|
38
45
|
end
|
|
39
46
|
data_points.clear
|
|
@@ -43,6 +50,8 @@ module OpenTelemetry
|
|
|
43
50
|
data_points.values.map! do |hdp|
|
|
44
51
|
hdp.start_time_unix_nano ||= start_time # Start time of a data point is from the first observation.
|
|
45
52
|
hdp.time_unix_nano = end_time
|
|
53
|
+
reservoir = @exemplar_reservoir_storage[hdp.attributes]
|
|
54
|
+
hdp.exemplars = reservoir&.collect(attributes: hdp.attributes, aggregation_temporality: @aggregation_temporality)
|
|
46
55
|
hdp = hdp.dup
|
|
47
56
|
hdp.bucket_counts = hdp.bucket_counts.dup
|
|
48
57
|
hdp
|
|
@@ -50,7 +59,7 @@ module OpenTelemetry
|
|
|
50
59
|
end
|
|
51
60
|
end
|
|
52
61
|
|
|
53
|
-
def update(amount, attributes, data_points)
|
|
62
|
+
def update(amount, attributes, data_points, exemplar_offer: false)
|
|
54
63
|
hdp = data_points.fetch(attributes) do
|
|
55
64
|
if @record_min_max
|
|
56
65
|
min = Float::INFINITY
|
|
@@ -71,6 +80,20 @@ module OpenTelemetry
|
|
|
71
80
|
)
|
|
72
81
|
end
|
|
73
82
|
|
|
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
|
|
89
|
+
|
|
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)
|
|
95
|
+
end
|
|
96
|
+
|
|
74
97
|
if @record_min_max
|
|
75
98
|
hdp.max = amount if amount > hdp.max
|
|
76
99
|
hdp.min = amount if amount < hdp.min
|
|
@@ -25,13 +25,20 @@ module OpenTelemetry
|
|
|
25
25
|
MIN_MAX_SIZE = 2
|
|
26
26
|
MAX_MAX_SIZE = 16_384
|
|
27
27
|
|
|
28
|
+
attr_reader :exemplar_reservoir
|
|
29
|
+
|
|
30
|
+
# if no reservoir pass from instrument, then use this empty reservoir to avoid no method found error
|
|
31
|
+
DEFAULT_RESERVOIR = Metrics::Exemplar::SimpleFixedSizeExemplarReservoir.new
|
|
32
|
+
private_constant :DEFAULT_RESERVOIR
|
|
33
|
+
|
|
28
34
|
# The default boundaries are calculated based on default max_size and max_scale values
|
|
29
35
|
def initialize(
|
|
30
36
|
aggregation_temporality: ENV.fetch('OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE', :delta),
|
|
31
37
|
max_size: DEFAULT_SIZE,
|
|
32
38
|
max_scale: DEFAULT_SCALE,
|
|
33
39
|
record_min_max: true,
|
|
34
|
-
zero_threshold: 0
|
|
40
|
+
zero_threshold: 0,
|
|
41
|
+
exemplar_reservoir: nil
|
|
35
42
|
)
|
|
36
43
|
@aggregation_temporality = AggregationTemporality.determine_temporality(aggregation_temporality: aggregation_temporality, default: :delta)
|
|
37
44
|
@record_min_max = record_min_max
|
|
@@ -44,6 +51,11 @@ module OpenTelemetry
|
|
|
44
51
|
@size = validate_size(max_size)
|
|
45
52
|
@scale = validate_scale(max_scale)
|
|
46
53
|
|
|
54
|
+
@exemplar_reservoir = exemplar_reservoir || DEFAULT_RESERVOIR
|
|
55
|
+
@exemplar_reservoir_storage = {}
|
|
56
|
+
|
|
57
|
+
@mapping = new_mapping(@scale)
|
|
58
|
+
|
|
47
59
|
# Previous state for cumulative aggregation
|
|
48
60
|
@previous_positive = {} # nil
|
|
49
61
|
@previous_negative = {} # nil
|
|
@@ -67,6 +79,8 @@ module OpenTelemetry
|
|
|
67
79
|
hdps = data_points.values.map! do |hdp|
|
|
68
80
|
hdp.start_time_unix_nano = start_time
|
|
69
81
|
hdp.time_unix_nano = end_time
|
|
82
|
+
reservoir = @exemplar_reservoir_storage[hdp.attributes]
|
|
83
|
+
hdp.exemplars = reservoir&.collect(attributes: hdp.attributes, aggregation_temporality: @aggregation_temporality)
|
|
70
84
|
hdp
|
|
71
85
|
end
|
|
72
86
|
data_points.clear
|
|
@@ -146,6 +160,7 @@ module OpenTelemetry
|
|
|
146
160
|
@previous_scale[attributes] = min_scale
|
|
147
161
|
|
|
148
162
|
# Create merged data point
|
|
163
|
+
reservoir = @exemplar_reservoir_storage[attributes]
|
|
149
164
|
merged_hdp = ExponentialHistogramDataPoint.new(
|
|
150
165
|
attributes,
|
|
151
166
|
start_time,
|
|
@@ -157,7 +172,7 @@ module OpenTelemetry
|
|
|
157
172
|
@previous_positive[attributes].dup,
|
|
158
173
|
@previous_negative[attributes].dup,
|
|
159
174
|
0, # flags
|
|
160
|
-
|
|
175
|
+
reservoir&.collect(attributes: attributes, aggregation_temporality: @aggregation_temporality), # exemplars
|
|
161
176
|
@previous_min[attributes],
|
|
162
177
|
@previous_max[attributes],
|
|
163
178
|
@zero_threshold
|
|
@@ -172,6 +187,7 @@ module OpenTelemetry
|
|
|
172
187
|
# so return last merged data points if exists
|
|
173
188
|
if data_points.empty? && !@previous_positive.empty?
|
|
174
189
|
@previous_positive.each_key do |attributes|
|
|
190
|
+
reservoir = @exemplar_reservoir_storage[attributes]
|
|
175
191
|
merged_hdp = ExponentialHistogramDataPoint.new(
|
|
176
192
|
attributes,
|
|
177
193
|
start_time,
|
|
@@ -183,7 +199,7 @@ module OpenTelemetry
|
|
|
183
199
|
@previous_positive[attributes].dup,
|
|
184
200
|
@previous_negative[attributes].dup,
|
|
185
201
|
0, # flags
|
|
186
|
-
|
|
202
|
+
reservoir&.collect(attributes: attributes, aggregation_temporality: @aggregation_temporality), # exemplars
|
|
187
203
|
@previous_min[attributes],
|
|
188
204
|
@previous_max[attributes],
|
|
189
205
|
@zero_threshold
|
|
@@ -206,7 +222,7 @@ module OpenTelemetry
|
|
|
206
222
|
|
|
207
223
|
# this is aggregate in python; there is no merge in aggregate; but rescale happened
|
|
208
224
|
# rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
|
|
209
|
-
def update(amount, attributes, data_points)
|
|
225
|
+
def update(amount, attributes, data_points, exemplar_offer: false)
|
|
210
226
|
# fetch or initialize the ExponentialHistogramDataPoint
|
|
211
227
|
hdp = data_points.fetch(attributes) do
|
|
212
228
|
if @record_min_max
|
|
@@ -233,6 +249,20 @@ module OpenTelemetry
|
|
|
233
249
|
)
|
|
234
250
|
end
|
|
235
251
|
|
|
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
|
|
258
|
+
|
|
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)
|
|
264
|
+
end
|
|
265
|
+
|
|
236
266
|
# Start to populate the data point (esp. the buckets)
|
|
237
267
|
if @record_min_max
|
|
238
268
|
hdp.max = amount if amount > hdp.max
|
|
@@ -336,9 +366,6 @@ module OpenTelemetry
|
|
|
336
366
|
end
|
|
337
367
|
|
|
338
368
|
def get_scale_change(low, high)
|
|
339
|
-
# puts "get_scale_change: low: #{low}, high: #{high}, @size: #{@size}"
|
|
340
|
-
# python code also produce 18 with 0,1048575, the high is little bit off
|
|
341
|
-
# just checked, the mapping is also ok, produce the 1048575
|
|
342
369
|
change = 0
|
|
343
370
|
while high - low >= @size
|
|
344
371
|
high >>= 1
|
|
@@ -10,17 +10,44 @@ module OpenTelemetry
|
|
|
10
10
|
module Aggregation
|
|
11
11
|
# Contains the implementation of the LastValue aggregation
|
|
12
12
|
class LastValue
|
|
13
|
+
attr_reader :exemplar_reservoir
|
|
14
|
+
|
|
15
|
+
# if no reservoir pass from instrument, then use this empty reservoir to avoid no method found error
|
|
16
|
+
DEFAULT_RESERVOIR = Metrics::Exemplar::SimpleFixedSizeExemplarReservoir.new
|
|
17
|
+
private_constant :DEFAULT_RESERVOIR
|
|
18
|
+
|
|
19
|
+
def initialize(exemplar_reservoir: nil)
|
|
20
|
+
@exemplar_reservoir = exemplar_reservoir || DEFAULT_RESERVOIR
|
|
21
|
+
@exemplar_reservoir_storage = {}
|
|
22
|
+
end
|
|
23
|
+
|
|
13
24
|
def collect(start_time, end_time, data_points)
|
|
14
25
|
ndps = data_points.values.map! do |ndp|
|
|
15
26
|
ndp.start_time_unix_nano = start_time
|
|
16
27
|
ndp.time_unix_nano = end_time
|
|
28
|
+
reservoir = @exemplar_reservoir_storage[ndp.attributes]
|
|
29
|
+
ndp.exemplars = reservoir&.collect(attributes: ndp.attributes, aggregation_temporality: :delta)
|
|
17
30
|
ndp
|
|
18
31
|
end
|
|
19
32
|
data_points.clear
|
|
20
33
|
ndps
|
|
21
34
|
end
|
|
22
35
|
|
|
23
|
-
def update(increment, attributes, data_points)
|
|
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
|
|
43
|
+
|
|
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
|
|
50
|
+
|
|
24
51
|
data_points[attributes] = NumberDataPoint.new(
|
|
25
52
|
attributes,
|
|
26
53
|
nil,
|
|
@@ -11,9 +11,20 @@ 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
|
-
|
|
14
|
+
attr_reader :exemplar_reservoir
|
|
15
|
+
|
|
16
|
+
# if no reservior pass from instrument, then use this empty reservior to avoid no method found error
|
|
17
|
+
DEFAULT_RESERVOIR = Metrics::Exemplar::SimpleFixedSizeExemplarReservoir.new
|
|
18
|
+
private_constant :DEFAULT_RESERVOIR
|
|
19
|
+
|
|
20
|
+
def initialize(aggregation_temporality: ENV.fetch('OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE', :cumulative),
|
|
21
|
+
monotonic: false,
|
|
22
|
+
instrument_kind: nil,
|
|
23
|
+
exemplar_reservoir: nil)
|
|
15
24
|
@aggregation_temporality = AggregationTemporality.determine_temporality(aggregation_temporality: aggregation_temporality, instrument_kind: instrument_kind, default: :cumulative)
|
|
16
25
|
@monotonic = monotonic
|
|
26
|
+
@exemplar_reservoir = exemplar_reservoir || DEFAULT_RESERVOIR
|
|
27
|
+
@exemplar_reservoir_storage = {}
|
|
17
28
|
end
|
|
18
29
|
|
|
19
30
|
def collect(start_time, end_time, data_points)
|
|
@@ -22,6 +33,8 @@ module OpenTelemetry
|
|
|
22
33
|
ndps = data_points.values.map! do |ndp|
|
|
23
34
|
ndp.start_time_unix_nano = start_time
|
|
24
35
|
ndp.time_unix_nano = end_time
|
|
36
|
+
reservoir = @exemplar_reservoir_storage[ndp.attributes]
|
|
37
|
+
ndp.exemplars = reservoir&.collect(attributes: ndp.attributes, aggregation_temporality: @aggregation_temporality)
|
|
25
38
|
ndp
|
|
26
39
|
end
|
|
27
40
|
data_points.clear
|
|
@@ -31,6 +44,8 @@ module OpenTelemetry
|
|
|
31
44
|
data_points.values.map! do |ndp|
|
|
32
45
|
ndp.start_time_unix_nano ||= start_time # Start time of a data point is from the first observation.
|
|
33
46
|
ndp.time_unix_nano = end_time
|
|
47
|
+
reservoir = @exemplar_reservoir_storage[ndp.attributes]
|
|
48
|
+
ndp.exemplars = reservoir&.collect(attributes: ndp.attributes, aggregation_temporality: @aggregation_temporality)
|
|
34
49
|
ndp.dup
|
|
35
50
|
end
|
|
36
51
|
end
|
|
@@ -40,7 +55,7 @@ module OpenTelemetry
|
|
|
40
55
|
@monotonic
|
|
41
56
|
end
|
|
42
57
|
|
|
43
|
-
def update(increment, attributes, data_points)
|
|
58
|
+
def update(increment, attributes, data_points, exemplar_offer: false)
|
|
44
59
|
return if @monotonic && increment < 0
|
|
45
60
|
|
|
46
61
|
ndp = data_points[attributes] || data_points[attributes] = NumberDataPoint.new(
|
|
@@ -51,6 +66,20 @@ module OpenTelemetry
|
|
|
51
66
|
nil
|
|
52
67
|
)
|
|
53
68
|
|
|
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
|
+
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
|
|
82
|
+
|
|
54
83
|
ndp.value += increment
|
|
55
84
|
nil
|
|
56
85
|
end
|