Skip to content

Commit c52c5fe

Browse files
authored
Custom histogram metric buckets (#249)
Fixes #228
1 parent 71af6d6 commit c52c5fe

File tree

6 files changed

+82
-10
lines changed

6 files changed

+82
-10
lines changed

temporalio/ext/src/runtime.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use temporal_sdk_core::telemetry::{
1414
build_otlp_metric_exporter, start_prometheus_metric_exporter, MetricsCallBuffer,
1515
};
1616
use temporal_sdk_core::{CoreRuntime, TokioRuntimeBuilder};
17+
use temporal_sdk_core_api::telemetry::HistogramBucketOverrides;
1718
use temporal_sdk_core_api::telemetry::{
1819
metrics::MetricCallBufferer, Logger, MetricTemporality, OtelCollectorOptionsBuilder,
1920
OtlpProtocol, PrometheusExporterOptionsBuilder, TelemetryOptionsBuilder,
@@ -129,6 +130,9 @@ impl Runtime {
129130
if opentelemetry.member::<bool>(id!("http"))? {
130131
opts_build.protocol(OtlpProtocol::Http);
131132
}
133+
if let Some(overrides) = opentelemetry.member::<Option<HashMap<String, Vec<f64>>>>(id!("histogram_bucket_overrides"))? {
134+
opts_build.histogram_bucket_overrides(HistogramBucketOverrides { overrides });
135+
}
132136
let opts = opts_build
133137
.build()
134138
.map_err(|err| error!("Invalid OpenTelemetry options: {}", err))?;
@@ -150,6 +154,9 @@ impl Runtime {
150154
if let Some(global_tags) = metrics.member::<Option<HashMap<String, String>>>(id!("global_tags"))? {
151155
opts_build.global_tags(global_tags);
152156
}
157+
if let Some(overrides) = prom.member::<Option<HashMap<String, Vec<f64>>>>(id!("histogram_bucket_overrides"))? {
158+
opts_build.histogram_bucket_overrides(HistogramBucketOverrides { overrides });
159+
}
153160
let opts = opts_build
154161
.build()
155162
.map_err(|err| error!("Invalid Prometheus options: {}", err))?;

temporalio/lib/temporalio/internal/bridge/runtime.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ class Runtime
3838
:metric_temporality_delta,
3939
:durations_as_seconds,
4040
:http,
41+
:histogram_bucket_overrides, # Optional
4142
keyword_init: true
4243
)
4344

@@ -46,6 +47,7 @@ class Runtime
4647
:counters_total_suffix,
4748
:unit_suffix,
4849
:durations_as_seconds,
50+
:histogram_bucket_overrides, # Optional
4951
keyword_init: true
5052
)
5153
end

temporalio/lib/temporalio/runtime.rb

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,8 @@ def _to_bridge
178178
:metric_periodicity,
179179
:metric_temporality,
180180
:durations_as_seconds,
181-
:http
181+
:http,
182+
:histogram_bucket_overrides
182183
)
183184

184185
# Options for exporting metrics to OpenTelemetry.
@@ -197,6 +198,9 @@ def _to_bridge
197198
# +false+.
198199
# @!attribute http
199200
# @return [Boolean] True if the protocol is HTTP, false if gRPC (the default).
201+
# @!attribute histogram_bucket_overrides
202+
# @return [Hash<String, Array<Numeric>>, nil] Override default histogram buckets. Key of the hash it the metric
203+
# name, value is an array of floats for the set of buckets.
200204
class OpenTelemetryMetricsOptions
201205
# OpenTelemetry metric temporality.
202206
module MetricTemporality
@@ -213,13 +217,16 @@ module MetricTemporality
213217
# @param durations_as_seconds [Boolean] Whether to use float seconds instead of integer milliseconds for
214218
# durations.
215219
# @param http [Boolean] True if the protocol is HTTP, false if gRPC (the default).
220+
# @param histogram_bucket_overrides [Hash<String, Array<Numeric>>, nil] Override default histogram buckets. Key of
221+
# the hash it the metric name, value is an array of floats for the set of buckets.
216222
def initialize(
217223
url:,
218224
headers: nil,
219225
metric_periodicity: nil,
220226
metric_temporality: MetricTemporality::CUMULATIVE,
221227
durations_as_seconds: false,
222-
http: false
228+
http: false,
229+
histogram_bucket_overrides: nil
223230
)
224231
super
225232
end
@@ -237,7 +244,8 @@ def _to_bridge
237244
else raise 'Unrecognized metric temporality'
238245
end,
239246
durations_as_seconds:,
240-
http:
247+
http:,
248+
histogram_bucket_overrides:
241249
)
242250
end
243251
end
@@ -246,7 +254,8 @@ def _to_bridge
246254
:bind_address,
247255
:counters_total_suffix,
248256
:unit_suffix,
249-
:durations_as_seconds
257+
:durations_as_seconds,
258+
:histogram_bucket_overrides
250259
)
251260

252261
# Options for exporting metrics to Prometheus.
@@ -259,6 +268,9 @@ def _to_bridge
259268
# @return [Boolean] If `true`, all histograms will include the unit in their name as a suffix.
260269
# @!attribute durations_as_seconds
261270
# @return [Boolean] Whether to use float seconds instead of integer milliseconds for durations.
271+
# @!attribute histogram_bucket_overrides
272+
# @return [Hash<String, Array<Numeric>>, nil] Override default histogram buckets. Key of the hash it the metric
273+
# name, value is an array of floats for the set of buckets.
262274
class PrometheusMetricsOptions
263275
# Create Prometheus options.
264276
#
@@ -267,11 +279,14 @@ class PrometheusMetricsOptions
267279
# @param unit_suffix [Boolean] If `true`, all histograms will include the unit in their name as a suffix.
268280
# @param durations_as_seconds [Boolean] Whether to use float seconds instead of integer milliseconds for
269281
# durations.
282+
# @param histogram_bucket_overrides [Hash<String, Array<Numeric>>, nil] Override default histogram buckets. Key of
283+
# the hash it the metric name, value is an array of floats for the set of buckets.
270284
def initialize(
271285
bind_address:,
272286
counters_total_suffix: false,
273287
unit_suffix: false,
274-
durations_as_seconds: false
288+
durations_as_seconds: false,
289+
histogram_bucket_overrides: nil
275290
)
276291
super
277292
end
@@ -283,7 +298,8 @@ def _to_bridge
283298
bind_address:,
284299
counters_total_suffix:,
285300
unit_suffix:,
286-
durations_as_seconds:
301+
durations_as_seconds:,
302+
histogram_bucket_overrides:
287303
)
288304
end
289305
end

temporalio/sig/temporalio/internal/bridge/runtime.rbs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,16 @@ module Temporalio
5151
attr_accessor metric_temporality_delta: bool
5252
attr_accessor durations_as_seconds: bool
5353
attr_accessor http: bool
54+
attr_accessor histogram_bucket_overrides: Hash[String, Array[Numeric]]?
5455

5556
def initialize: (
5657
url: String,
5758
headers: Hash[String, String]?,
5859
metric_periodicity: Float?,
5960
metric_temporality_delta: bool,
6061
durations_as_seconds: bool,
61-
http: bool
62+
http: bool,
63+
histogram_bucket_overrides: Hash[String, Array[Numeric]]?
6264
) -> void
6365
end
6466

@@ -67,12 +69,14 @@ module Temporalio
6769
attr_accessor counters_total_suffix: bool
6870
attr_accessor unit_suffix: bool
6971
attr_accessor durations_as_seconds: bool
72+
attr_accessor histogram_bucket_overrides: Hash[String, Array[Numeric]]?
7073

7174
def initialize: (
7275
bind_address: String,
7376
counters_total_suffix: bool,
7477
unit_suffix: bool,
75-
durations_as_seconds: bool
78+
durations_as_seconds: bool,
79+
histogram_bucket_overrides: Hash[String, Array[Numeric]]?
7680
) -> void
7781
end
7882

temporalio/sig/temporalio/runtime.rbs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,14 +70,16 @@ module Temporalio
7070
attr_reader metric_temporality: MetricTemporality
7171
attr_reader durations_as_seconds: bool
7272
attr_reader http: bool
73+
attr_reader histogram_bucket_overrides: Hash[String, Array[Numeric]]?
7374

7475
def initialize: (
7576
url: String,
7677
?headers: Hash[String, String]?,
7778
?metric_periodicity: Float?,
7879
?metric_temporality: MetricTemporality,
7980
?durations_as_seconds: bool,
80-
?http: bool
81+
?http: bool,
82+
?histogram_bucket_overrides: Hash[String, Array[Numeric]]?
8183
) -> void
8284

8385
def _to_bridge: -> Internal::Bridge::Runtime::OpenTelemetryMetricsOptions
@@ -88,12 +90,14 @@ module Temporalio
8890
attr_reader counters_total_suffix: bool
8991
attr_reader unit_suffix: bool
9092
attr_reader durations_as_seconds: bool
93+
attr_reader histogram_bucket_overrides: Hash[String, Array[Numeric]]?
9194

9295
def initialize: (
9396
bind_address: String,
9497
?counters_total_suffix: bool,
9598
?unit_suffix: bool,
96-
?durations_as_seconds: bool
99+
?durations_as_seconds: bool,
100+
?histogram_bucket_overrides: Hash[String, Array[Numeric]]?
97101
) -> void
98102

99103
def _to_bridge: -> Internal::Bridge::Runtime::PrometheusMetricsOptions

temporalio/test/runtime_test.rb

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,4 +127,43 @@ def test_metric_basics
127127
assert_bad_call { counter_int.record(1, additional_attributes: { 123 => 'foo' }) }
128128
# steep:ignore:end
129129
end
130+
131+
def test_histogram_bucket_overrides
132+
# Prom metrics with custom histogram buckets
133+
prom_addr = "127.0.0.1:#{find_free_port}"
134+
runtime = Temporalio::Runtime.new(
135+
telemetry: Temporalio::Runtime::TelemetryOptions.new(
136+
metrics: Temporalio::Runtime::MetricsOptions.new(
137+
prometheus: Temporalio::Runtime::PrometheusMetricsOptions.new(
138+
bind_address: prom_addr,
139+
histogram_bucket_overrides: {
140+
'temporal_request_latency' => [123.4, 567.89],
141+
'custom_histogram' => [5, 6, 7]
142+
}
143+
)
144+
)
145+
)
146+
)
147+
conn_opts = env.client.connection.options.with(runtime:)
148+
client_opts = env.client.options.with(
149+
connection: Temporalio::Client::Connection.new(**conn_opts.to_h) # steep:ignore
150+
)
151+
client = Temporalio::Client.new(**client_opts.to_h) # steep:ignore
152+
153+
# Generate metrics
154+
client.workflow_service.get_system_info(Temporalio::Api::WorkflowService::V1::GetSystemInfoRequest.new)
155+
hist = runtime.metric_meter.create_metric(:histogram, 'custom_histogram', value_type: :float)
156+
hist.record(4.5)
157+
hist.record(5.5)
158+
hist.record(6.5)
159+
hist.record(7.5)
160+
161+
# Check metrics
162+
dump = Net::HTTP.get(URI("http://#{prom_addr}/metrics"))
163+
assert_metric_line(dump, 'temporal_request_latency_bucket', le: '123.4')
164+
assert_metric_line(dump, 'temporal_request_latency_bucket', le: '567.89')
165+
assert_equal '1', assert_metric_line(dump, 'custom_histogram_bucket', le: '5')
166+
assert_equal '2', assert_metric_line(dump, 'custom_histogram_bucket', le: '6')
167+
assert_equal '3', assert_metric_line(dump, 'custom_histogram_bucket', le: '7')
168+
end
130169
end

0 commit comments

Comments
 (0)