Skip to content

Commit 464b895

Browse files
authored
Custom histogram metric buckets (#456)
Fixes #416
1 parent 5531bfe commit 464b895

18 files changed

Lines changed: 143 additions & 27 deletions

src/Temporalio/Bridge/Interop/Interop.cs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,9 @@ internal partial struct OpenTelemetryOptions
331331

332332
[NativeTypeName("enum OpenTelemetryProtocol")]
333333
public OpenTelemetryProtocol protocol;
334+
335+
[NativeTypeName("MetadataRef")]
336+
public ByteArrayRef histogram_bucket_overrides;
334337
}
335338

336339
internal partial struct PrometheusOptions
@@ -346,6 +349,9 @@ internal partial struct PrometheusOptions
346349

347350
[NativeTypeName("bool")]
348351
public byte durations_as_seconds;
352+
353+
[NativeTypeName("MetadataRef")]
354+
public ByteArrayRef histogram_bucket_overrides;
349355
}
350356

351357
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
@@ -646,7 +652,7 @@ internal unsafe partial struct SlotInfo
646652
{
647653
public SlotInfo_Tag tag;
648654

649-
[NativeTypeName("__AnonymousRecord_temporal-sdk-bridge_L443_C3")]
655+
[NativeTypeName("__AnonymousRecord_temporal-sdk-bridge_L453_C3")]
650656
public _Anonymous_e__Union Anonymous;
651657

652658
internal ref WorkflowSlotInfo_Body workflow_slot_info
@@ -775,7 +781,7 @@ internal unsafe partial struct SlotSupplier
775781
{
776782
public SlotSupplier_Tag tag;
777783

778-
[NativeTypeName("__AnonymousRecord_temporal-sdk-bridge_L494_C3")]
784+
[NativeTypeName("__AnonymousRecord_temporal-sdk-bridge_L504_C3")]
779785
public _Anonymous_e__Union Anonymous;
780786

781787
internal ref FixedSizeSlotSupplier fixed_size
@@ -815,15 +821,15 @@ internal ref CustomSlotSupplierCallbacksImpl custom
815821
internal unsafe partial struct _Anonymous_e__Union
816822
{
817823
[FieldOffset(0)]
818-
[NativeTypeName("__AnonymousRecord_temporal-sdk-bridge_L495_C5")]
824+
[NativeTypeName("__AnonymousRecord_temporal-sdk-bridge_L505_C5")]
819825
public _Anonymous1_e__Struct Anonymous1;
820826

821827
[FieldOffset(0)]
822-
[NativeTypeName("__AnonymousRecord_temporal-sdk-bridge_L498_C5")]
828+
[NativeTypeName("__AnonymousRecord_temporal-sdk-bridge_L508_C5")]
823829
public _Anonymous2_e__Struct Anonymous2;
824830

825831
[FieldOffset(0)]
826-
[NativeTypeName("__AnonymousRecord_temporal-sdk-bridge_L501_C5")]
832+
[NativeTypeName("__AnonymousRecord_temporal-sdk-bridge_L511_C5")]
827833
public _Anonymous3_e__Struct Anonymous3;
828834

829835
internal partial struct _Anonymous1_e__Struct

src/Temporalio/Bridge/OptionsExtensions.cs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ public static unsafe Interop.OpenTelemetryOptions ToInteropOptions(
108108
metric_temporality = temporality,
109109
durations_as_seconds = (byte)(options.UseSecondsForDuration ? 1 : 0),
110110
protocol = protocol,
111+
histogram_bucket_overrides = ToHistogramBucketOverrides(
112+
options.HistogramBucketOverrides, scope),
111113
};
112114
}
113115

@@ -131,6 +133,8 @@ public static unsafe Interop.PrometheusOptions ToInteropOptions(
131133
counters_total_suffix = (byte)(options.HasCounterTotalSuffix ? 1 : 0),
132134
unit_suffix = (byte)(options.HasUnitSuffix ? 1 : 0),
133135
durations_as_seconds = (byte)(options.UseSecondsForDuration ? 1 : 0),
136+
histogram_bucket_overrides = ToHistogramBucketOverrides(
137+
options.HistogramBucketOverrides, scope),
134138
};
135139
}
136140

@@ -180,11 +184,7 @@ public static unsafe Interop.MetricsOptions ToInteropOptions(
180184
{
181185
throw new ArgumentException("Prometheus options must have bind address");
182186
}
183-
prometheus = scope.Pointer(
184-
new Interop.PrometheusOptions()
185-
{
186-
bind_address = scope.ByteArray(options.Prometheus.BindAddress),
187-
});
187+
prometheus = scope.Pointer(options.Prometheus.ToInteropOptions(scope));
188188
}
189189
else if (options.OpenTelemetry != null)
190190
{
@@ -554,6 +554,12 @@ public static Interop.WorkerOptions ToInteropOptions(
554554
};
555555
}
556556

557+
private static Interop.ByteArrayRef ToHistogramBucketOverrides(
558+
IReadOnlyDictionary<string, IReadOnlyCollection<double>>? overrides, Scope scope) =>
559+
scope.Metadata(overrides?.Select(kvp =>
560+
new KeyValuePair<string, string>(
561+
kvp.Key, string.Join(",", kvp.Value.Select(v => v.ToString("0.###"))))));
562+
557563
private static Interop.TunerHolder ToInteropTuner(
558564
this Temporalio.Worker.Tuning.WorkerTuner tuner,
559565
Scope scope,

src/Temporalio/Bridge/include/temporal-sdk-bridge.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,13 +223,23 @@ typedef struct OpenTelemetryOptions {
223223
enum OpenTelemetryMetricTemporality metric_temporality;
224224
bool durations_as_seconds;
225225
enum OpenTelemetryProtocol protocol;
226+
/**
227+
* Histogram bucket overrides in form of
228+
* <metric1>\n<float>,<float>,<float>\n<metric2>\n<float>,<float>,<float>
229+
*/
230+
MetadataRef histogram_bucket_overrides;
226231
} OpenTelemetryOptions;
227232

228233
typedef struct PrometheusOptions {
229234
struct ByteArrayRef bind_address;
230235
bool counters_total_suffix;
231236
bool unit_suffix;
232237
bool durations_as_seconds;
238+
/**
239+
* Histogram bucket overrides in form of
240+
* <metric1>\n<float>,<float>,<float>\n<metric2>\n<float>,<float>,<float>
241+
*/
242+
MetadataRef histogram_bucket_overrides;
233243
} PrometheusOptions;
234244

235245
typedef const void *(*CustomMetricMeterMetricNewCallback)(struct ByteArrayRef name,

src/Temporalio/Bridge/src/runtime.rs

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use crate::ByteArrayRef;
55
use crate::MetadataRef;
66

77
use serde_json::json;
8+
use std::collections::HashMap;
89
use std::fmt;
910
use std::net::SocketAddr;
1011
use std::str::FromStr;
@@ -17,6 +18,7 @@ use temporal_sdk_core::telemetry::{build_otlp_metric_exporter, start_prometheus_
1718
use temporal_sdk_core::CoreRuntime;
1819
use temporal_sdk_core::TokioRuntimeBuilder;
1920
use temporal_sdk_core_api::telemetry::metrics::CoreMeter;
21+
use temporal_sdk_core_api::telemetry::HistogramBucketOverrides;
2022
use temporal_sdk_core_api::telemetry::MetricTemporality;
2123
use temporal_sdk_core_api::telemetry::{CoreLog, CoreLogConsumer};
2224
use temporal_sdk_core_api::telemetry::{
@@ -85,6 +87,9 @@ pub struct OpenTelemetryOptions {
8587
metric_temporality: OpenTelemetryMetricTemporality,
8688
durations_as_seconds: bool,
8789
protocol: OpenTelemetryProtocol,
90+
/// Histogram bucket overrides in form of
91+
/// <metric1>\n<float>,<float>,<float>\n<metric2>\n<float>,<float>,<float>
92+
histogram_bucket_overrides: MetadataRef,
8893
}
8994

9095
#[repr(C)]
@@ -105,6 +110,9 @@ pub struct PrometheusOptions {
105110
counters_total_suffix: bool,
106111
unit_suffix: bool,
107112
durations_as_seconds: bool,
113+
/// Histogram bucket overrides in form of
114+
/// <metric1>\n<float>,<float>,<float>\n<metric2>\n<float>,<float>,<float>
115+
histogram_bucket_overrides: MetadataRef,
108116
}
109117

110118
#[derive(Clone)]
@@ -378,7 +386,12 @@ fn create_meter(
378386
OpenTelemetryMetricTemporality::Delta => MetricTemporality::Delta,
379387
})
380388
.global_tags(options.global_tags.to_string_map_on_newlines())
381-
.use_seconds_for_durations(otel_options.durations_as_seconds);
389+
.use_seconds_for_durations(otel_options.durations_as_seconds)
390+
.histogram_bucket_overrides(HistogramBucketOverrides {
391+
overrides: parse_histogram_bucket_overrides(
392+
&otel_options.histogram_bucket_overrides,
393+
)?,
394+
});
382395
if otel_options.metric_periodicity_millis > 0 {
383396
build.metric_periodicity(Duration::from_millis(
384397
otel_options.metric_periodicity_millis.into(),
@@ -398,7 +411,12 @@ fn create_meter(
398411
.global_tags(options.global_tags.to_string_map_on_newlines())
399412
.counters_total_suffix(prom_options.counters_total_suffix)
400413
.unit_suffix(prom_options.unit_suffix)
401-
.use_seconds_for_durations(prom_options.durations_as_seconds);
414+
.use_seconds_for_durations(prom_options.durations_as_seconds)
415+
.histogram_bucket_overrides(HistogramBucketOverrides {
416+
overrides: parse_histogram_bucket_overrides(
417+
&prom_options.histogram_bucket_overrides,
418+
)?,
419+
});
402420
Ok(start_prometheus_metric_exporter(build.build()?)?.meter)
403421
} else if let Some(custom_meter) = custom_meter {
404422
Ok(Arc::new(custom_meter))
@@ -408,3 +426,19 @@ fn create_meter(
408426
))
409427
}
410428
}
429+
430+
fn parse_histogram_bucket_overrides(
431+
raw: &MetadataRef,
432+
) -> anyhow::Result<HashMap<String, Vec<f64>>> {
433+
raw.to_string_map_on_newlines()
434+
.into_iter()
435+
.map(|(k, v)| {
436+
let vals: anyhow::Result<Vec<f64>> = v
437+
.split(',')
438+
.map(str::parse::<f64>)
439+
.collect::<Result<_, _>>() // Result<Vec<f64>, ParseFloatError>
440+
.map_err(Into::into);
441+
vals.map(|vals| (k, vals))
442+
})
443+
.collect()
444+
}

src/Temporalio/Runtime/OpenTelemetryOptions.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,12 @@ public OpenTelemetryOptions(string url)
6262
/// </summary>
6363
public OpenTelemetryProtocol Protocol { get; set; } = OpenTelemetryProtocol.Grpc;
6464

65+
/// <summary>
66+
/// Gets or sets the histogram bucket overrides. The dictionary key is the metric name and
67+
/// the value is the collection of buckets as floats.
68+
/// </summary>
69+
public IReadOnlyDictionary<string, IReadOnlyCollection<double>>? HistogramBucketOverrides { get; set; }
70+
6571
/// <summary>
6672
/// Create a shallow copy of these options.
6773
/// </summary>

src/Temporalio/Runtime/PrometheusOptions.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23

34
namespace Temporalio.Runtime
45
{
@@ -41,6 +42,12 @@ public PrometheusOptions()
4142
/// </summary>
4243
public bool UseSecondsForDuration { get; set; }
4344

45+
/// <summary>
46+
/// Gets or sets the histogram bucket overrides. The dictionary key is the metric name and
47+
/// the value is the collection of buckets as floats.
48+
/// </summary>
49+
public IReadOnlyDictionary<string, IReadOnlyCollection<double>>? HistogramBucketOverrides { get; set; }
50+
4451
/// <summary>
4552
/// Create a shallow copy of these options.
4653
/// </summary>

tests/Temporalio.Tests/.editorconfig

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ dotnet_diagnostic.SA1118.severity = none
5252
# To test lambda attributes, lambda and attribute share line
5353
dotnet_diagnostic.SA1134.severity = none
5454

55+
# We want to have classes near their tests
56+
dotnet_diagnostic.SA1201.severity = none
57+
dotnet_diagnostic.SA1204.severity = none
58+
5559
# We allow underscores as names and Rosyln devs won't fix
5660
# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2599
5761
dotnet_diagnostic.SA1313.severity = none

tests/Temporalio.Tests/Client/TemporalClientWorkflowTests.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
#pragma warning disable SA1201, SA1204 // We want to have classes near their tests
21
namespace Temporalio.Tests.Client;
32

43
using Temporalio.Api.Enums.V1;

tests/Temporalio.Tests/Extensions/DiagnosticSource/CustomMetricMeterTests.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
#pragma warning disable SA1201, SA1204 // We want to have classes near their tests
21
namespace Temporalio.Tests.Extensions.DiagnosticSource;
32

43
using System.Collections.Concurrent;

tests/Temporalio.Tests/Extensions/Hosting/ActivityScopeTests.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
#pragma warning disable SA1201, SA1204 // We want to have classes near their tests
21
namespace Temporalio.Tests.Extensions.Hosting;
32

43
using System.Threading.Tasks;

0 commit comments

Comments
 (0)