@temporalio/core-bridge 1.12.0 → 1.12.2
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.
- package/Cargo.lock +64 -119
- package/Cargo.toml +1 -1
- package/index.js +3 -2
- package/package.json +3 -3
- package/releases/aarch64-apple-darwin/index.node +0 -0
- package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
- package/releases/x86_64-apple-darwin/index.node +0 -0
- package/releases/x86_64-pc-windows-msvc/index.node +0 -0
- package/releases/x86_64-unknown-linux-gnu/index.node +0 -0
- package/sdk-core/.cargo/config.toml +1 -2
- package/sdk-core/.github/workflows/per-pr.yml +2 -0
- package/sdk-core/AGENTS.md +7 -0
- package/sdk-core/Cargo.toml +9 -5
- package/sdk-core/README.md +6 -5
- package/sdk-core/client/Cargo.toml +3 -2
- package/sdk-core/client/src/lib.rs +17 -8
- package/sdk-core/client/src/metrics.rs +57 -23
- package/sdk-core/client/src/raw.rs +33 -15
- package/sdk-core/core/Cargo.toml +11 -9
- package/sdk-core/core/benches/workflow_replay.rs +114 -15
- package/sdk-core/core/src/core_tests/activity_tasks.rs +18 -18
- package/sdk-core/core/src/core_tests/child_workflows.rs +4 -4
- package/sdk-core/core/src/core_tests/determinism.rs +6 -6
- package/sdk-core/core/src/core_tests/local_activities.rs +20 -20
- package/sdk-core/core/src/core_tests/mod.rs +40 -5
- package/sdk-core/core/src/core_tests/queries.rs +25 -16
- package/sdk-core/core/src/core_tests/replay_flag.rs +3 -3
- package/sdk-core/core/src/core_tests/updates.rs +3 -3
- package/sdk-core/core/src/core_tests/workers.rs +9 -7
- package/sdk-core/core/src/core_tests/workflow_tasks.rs +40 -42
- package/sdk-core/core/src/ephemeral_server/mod.rs +1 -19
- package/sdk-core/core/src/lib.rs +10 -1
- package/sdk-core/core/src/pollers/poll_buffer.rs +2 -2
- package/sdk-core/core/src/replay/mod.rs +3 -3
- package/sdk-core/core/src/telemetry/metrics.rs +306 -152
- package/sdk-core/core/src/telemetry/mod.rs +11 -4
- package/sdk-core/core/src/telemetry/otel.rs +134 -131
- package/sdk-core/core/src/telemetry/prometheus_meter.rs +885 -0
- package/sdk-core/core/src/telemetry/prometheus_server.rs +48 -28
- package/sdk-core/core/src/test_help/mod.rs +27 -12
- package/sdk-core/core/src/worker/activities/activity_heartbeat_manager.rs +7 -7
- package/sdk-core/core/src/worker/activities.rs +4 -4
- package/sdk-core/core/src/worker/client/mocks.rs +10 -3
- package/sdk-core/core/src/worker/client.rs +68 -5
- package/sdk-core/core/src/worker/heartbeat.rs +229 -0
- package/sdk-core/core/src/worker/mod.rs +35 -14
- package/sdk-core/core/src/worker/tuner/resource_based.rs +4 -4
- package/sdk-core/core/src/worker/workflow/history_update.rs +71 -19
- package/sdk-core/core/src/worker/workflow/machines/cancel_external_state_machine.rs +1 -2
- package/sdk-core/core/src/worker/workflow/machines/child_workflow_state_machine.rs +1 -1
- package/sdk-core/core/src/worker/workflow/machines/nexus_operation_state_machine.rs +31 -48
- package/sdk-core/core/src/worker/workflow/machines/signal_external_state_machine.rs +1 -2
- package/sdk-core/core/src/worker/workflow/machines/upsert_search_attributes_state_machine.rs +3 -3
- package/sdk-core/core/src/worker/workflow/machines/workflow_machines.rs +4 -1
- package/sdk-core/core/src/worker/workflow/managed_run.rs +1 -1
- package/sdk-core/core/src/worker/workflow/mod.rs +15 -15
- package/sdk-core/core-api/Cargo.toml +2 -2
- package/sdk-core/core-api/src/envconfig.rs +204 -99
- package/sdk-core/core-api/src/lib.rs +9 -0
- package/sdk-core/core-api/src/telemetry/metrics.rs +548 -100
- package/sdk-core/core-api/src/worker.rs +11 -5
- package/sdk-core/core-c-bridge/Cargo.toml +49 -0
- package/sdk-core/core-c-bridge/build.rs +26 -0
- package/sdk-core/core-c-bridge/include/temporal-sdk-core-c-bridge.h +817 -0
- package/sdk-core/core-c-bridge/src/client.rs +679 -0
- package/sdk-core/core-c-bridge/src/lib.rs +245 -0
- package/sdk-core/core-c-bridge/src/metric.rs +682 -0
- package/sdk-core/core-c-bridge/src/random.rs +61 -0
- package/sdk-core/core-c-bridge/src/runtime.rs +445 -0
- package/sdk-core/core-c-bridge/src/testing.rs +282 -0
- package/sdk-core/core-c-bridge/src/tests/context.rs +644 -0
- package/sdk-core/core-c-bridge/src/tests/mod.rs +178 -0
- package/sdk-core/core-c-bridge/src/tests/utils.rs +108 -0
- package/sdk-core/core-c-bridge/src/worker.rs +1069 -0
- package/sdk-core/etc/deps.svg +64 -64
- package/sdk-core/sdk/src/activity_context.rs +6 -4
- package/sdk-core/sdk/src/lib.rs +49 -27
- package/sdk-core/sdk/src/workflow_future.rs +18 -25
- package/sdk-core/sdk-core-protos/protos/api_upstream/README.md +4 -0
- package/sdk-core/sdk-core-protos/protos/api_upstream/buf.yaml +0 -2
- package/sdk-core/sdk-core-protos/protos/api_upstream/openapi/openapiv2.json +630 -83
- package/sdk-core/sdk-core-protos/protos/api_upstream/openapi/openapiv3.yaml +632 -78
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/batch/v1/message.proto +4 -4
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/command/v1/message.proto +6 -4
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/common/v1/message.proto +2 -2
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/deployment/v1/message.proto +32 -2
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/common.proto +10 -1
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/deployment.proto +26 -0
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/failed_cause.proto +2 -0
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/reset.proto +4 -4
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/failure/v1/message.proto +2 -2
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/history/v1/message.proto +47 -31
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/nexus/v1/message.proto +4 -4
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/schedule/v1/message.proto +7 -1
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/worker/v1/message.proto +134 -0
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflow/v1/message.proto +14 -11
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +148 -37
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +21 -0
- package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +4 -4
- package/sdk-core/sdk-core-protos/src/history_builder.rs +9 -5
- package/sdk-core/sdk-core-protos/src/lib.rs +96 -6
- package/sdk-core/test-utils/src/lib.rs +11 -3
- package/sdk-core/tests/cloud_tests.rs +3 -3
- package/sdk-core/tests/heavy_tests.rs +11 -3
- package/sdk-core/tests/integ_tests/client_tests.rs +12 -13
- package/sdk-core/tests/integ_tests/ephemeral_server_tests.rs +1 -1
- package/sdk-core/tests/integ_tests/metrics_tests.rs +188 -83
- package/sdk-core/tests/integ_tests/polling_tests.rs +1 -1
- package/sdk-core/tests/integ_tests/queries_tests.rs +56 -40
- package/sdk-core/tests/integ_tests/update_tests.rs +2 -7
- package/sdk-core/tests/integ_tests/worker_tests.rs +3 -4
- package/sdk-core/tests/integ_tests/worker_versioning_tests.rs +3 -7
- package/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +3 -5
- package/sdk-core/tests/integ_tests/workflow_tests/nexus.rs +24 -17
- package/src/client.rs +6 -0
- package/src/metrics.rs +6 -6
|
@@ -0,0 +1,885 @@
|
|
|
1
|
+
use crate::{abstractions::dbg_panic, telemetry::default_buckets_for};
|
|
2
|
+
use anyhow::anyhow;
|
|
3
|
+
use parking_lot::RwLock;
|
|
4
|
+
use prometheus::{
|
|
5
|
+
GaugeVec, HistogramVec, IntCounterVec, IntGaugeVec, Opts,
|
|
6
|
+
core::{Collector, Desc, GenericCounter},
|
|
7
|
+
proto::{LabelPair, MetricFamily},
|
|
8
|
+
};
|
|
9
|
+
use std::{
|
|
10
|
+
collections::{BTreeMap, HashMap, HashSet, btree_map, hash_map},
|
|
11
|
+
fmt::{Debug, Formatter},
|
|
12
|
+
sync::Arc,
|
|
13
|
+
time::Duration,
|
|
14
|
+
};
|
|
15
|
+
use temporal_sdk_core_api::telemetry::metrics::{
|
|
16
|
+
CoreMeter, Counter, CounterBase, Gauge, GaugeBase, GaugeF64, GaugeF64Base, Histogram,
|
|
17
|
+
HistogramBase, HistogramDuration, HistogramDurationBase, HistogramF64, HistogramF64Base,
|
|
18
|
+
MetricAttributable, MetricAttributes, MetricParameters, NewAttributes, OrderedPromLabelSet,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
#[derive(derive_more::From, derive_more::TryInto, Debug, Clone)]
|
|
22
|
+
enum PromCollector {
|
|
23
|
+
Histo(HistogramVec),
|
|
24
|
+
Counter(IntCounterVec),
|
|
25
|
+
Gauge(IntGaugeVec),
|
|
26
|
+
GaugeF64(GaugeVec),
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
impl Collector for PromCollector {
|
|
30
|
+
fn desc(&self) -> Vec<&Desc> {
|
|
31
|
+
match self {
|
|
32
|
+
PromCollector::Histo(v) => v.desc(),
|
|
33
|
+
PromCollector::Counter(v) => v.desc(),
|
|
34
|
+
PromCollector::Gauge(v) => v.desc(),
|
|
35
|
+
PromCollector::GaugeF64(v) => v.desc(),
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
fn collect(&self) -> Vec<MetricFamily> {
|
|
40
|
+
match self {
|
|
41
|
+
PromCollector::Histo(v) => v.collect(),
|
|
42
|
+
PromCollector::Counter(v) => v.collect(),
|
|
43
|
+
PromCollector::Gauge(v) => v.collect(),
|
|
44
|
+
PromCollector::GaugeF64(v) => v.collect(),
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/// Replaces Prometheus's default registry with a custom one that allows us to register metrics that
|
|
50
|
+
/// have different label sets for the same name.
|
|
51
|
+
#[derive(Clone)]
|
|
52
|
+
pub(super) struct Registry {
|
|
53
|
+
collectors_by_id: Arc<RwLock<HashMap<u64, PromCollector>>>,
|
|
54
|
+
global_tags: BTreeMap<String, String>,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// A lot of this implementation code is lifted from the prometheus crate itself, and as such is a
|
|
58
|
+
// derivative work of the following:
|
|
59
|
+
// Copyright 2014 The Prometheus Authors
|
|
60
|
+
// Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0.
|
|
61
|
+
impl Registry {
|
|
62
|
+
pub(super) fn new(global_tags: HashMap<String, String>) -> Self {
|
|
63
|
+
Self {
|
|
64
|
+
collectors_by_id: Arc::new(RwLock::new(HashMap::new())),
|
|
65
|
+
global_tags: BTreeMap::from_iter(global_tags),
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Register a collector, potentially returning an existing collector that matches the provided
|
|
70
|
+
// one. In such cases the passed-in collector is discarded.
|
|
71
|
+
fn register<T: Into<PromCollector>>(&self, c: T) -> Option<PromCollector> {
|
|
72
|
+
let mut desc_id_set = HashSet::new();
|
|
73
|
+
let mut collector_id: u64 = 0;
|
|
74
|
+
let c = c.into();
|
|
75
|
+
|
|
76
|
+
for desc in c.desc() {
|
|
77
|
+
// If it is not a duplicate desc in this collector, add it to
|
|
78
|
+
// the collector_id. Here we assume that collectors (ie: metric vecs / histograms etc)
|
|
79
|
+
// should internally not repeat the same descriptor -- even though we allow entirely
|
|
80
|
+
// separate metrics with overlapping labels to be registered generally.
|
|
81
|
+
if desc_id_set.insert(desc.id) {
|
|
82
|
+
// Add the id and the dim hash, which includes both static and variable labels
|
|
83
|
+
collector_id = collector_id
|
|
84
|
+
.wrapping_add(desc.id)
|
|
85
|
+
.wrapping_add(desc.dim_hash);
|
|
86
|
+
} else {
|
|
87
|
+
dbg_panic!(
|
|
88
|
+
"Prometheus metric has duplicate descriptors, values may not be recorded on \
|
|
89
|
+
this metric. This is an SDK bug. Details: {:?}",
|
|
90
|
+
c.desc(),
|
|
91
|
+
);
|
|
92
|
+
return None;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
match self.collectors_by_id.write().entry(collector_id) {
|
|
96
|
+
hash_map::Entry::Vacant(vc) => {
|
|
97
|
+
vc.insert(c);
|
|
98
|
+
None
|
|
99
|
+
}
|
|
100
|
+
hash_map::Entry::Occupied(o) => Some(o.get().clone()),
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
pub(super) fn gather(&self) -> Vec<MetricFamily> {
|
|
105
|
+
let mut mf_by_name = BTreeMap::new();
|
|
106
|
+
|
|
107
|
+
for c in self.collectors_by_id.read().values() {
|
|
108
|
+
let mfs = c.collect();
|
|
109
|
+
for mut mf in mfs {
|
|
110
|
+
if mf.get_metric().is_empty() {
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
let name = mf.name().to_owned();
|
|
115
|
+
match mf_by_name.entry(name) {
|
|
116
|
+
btree_map::Entry::Vacant(entry) => {
|
|
117
|
+
entry.insert(mf);
|
|
118
|
+
}
|
|
119
|
+
btree_map::Entry::Occupied(mut entry) => {
|
|
120
|
+
let existent_mf = entry.get_mut();
|
|
121
|
+
let existent_metrics = existent_mf.mut_metric();
|
|
122
|
+
for metric in mf.take_metric().into_iter() {
|
|
123
|
+
existent_metrics.push(metric);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Now that MetricFamilies are all set, sort their Metrics
|
|
131
|
+
// lexicographically by their label values.
|
|
132
|
+
for mf in mf_by_name.values_mut() {
|
|
133
|
+
mf.mut_metric().sort_by(|m1, m2| {
|
|
134
|
+
let lps1 = m1.get_label();
|
|
135
|
+
let lps2 = m2.get_label();
|
|
136
|
+
|
|
137
|
+
if lps1.len() != lps2.len() {
|
|
138
|
+
return lps1.len().cmp(&lps2.len());
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
for (lp1, lp2) in lps1.iter().zip(lps2.iter()) {
|
|
142
|
+
if lp1.value() != lp2.value() {
|
|
143
|
+
return lp1.value().cmp(lp2.value());
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// We should never arrive here. Multiple metrics with the same
|
|
148
|
+
// label set in the same scrape will lead to undefined ingestion
|
|
149
|
+
// behavior. However, as above, we have to provide stable sorting
|
|
150
|
+
// here, even for inconsistent metrics. So sort equal metrics
|
|
151
|
+
// by their timestamp, with missing timestamps (implying "now")
|
|
152
|
+
// coming last.
|
|
153
|
+
m1.timestamp_ms().cmp(&m2.timestamp_ms())
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
mf_by_name
|
|
158
|
+
.into_values()
|
|
159
|
+
.map(|mut m| {
|
|
160
|
+
if self.global_tags.is_empty() {
|
|
161
|
+
return m;
|
|
162
|
+
}
|
|
163
|
+
// Add global labels
|
|
164
|
+
let pairs: Vec<LabelPair> = self
|
|
165
|
+
.global_tags
|
|
166
|
+
.iter()
|
|
167
|
+
.map(|(k, v)| {
|
|
168
|
+
let mut label = LabelPair::default();
|
|
169
|
+
label.set_name(k.to_string());
|
|
170
|
+
label.set_value(v.to_string());
|
|
171
|
+
label
|
|
172
|
+
})
|
|
173
|
+
.collect();
|
|
174
|
+
|
|
175
|
+
for metric in m.mut_metric().iter_mut() {
|
|
176
|
+
let mut labels: Vec<_> = metric.take_label();
|
|
177
|
+
labels.append(&mut pairs.clone());
|
|
178
|
+
metric.set_label(labels);
|
|
179
|
+
}
|
|
180
|
+
m
|
|
181
|
+
})
|
|
182
|
+
.collect()
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
impl Debug for Registry {
|
|
187
|
+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
188
|
+
write!(
|
|
189
|
+
f,
|
|
190
|
+
"Registry({} collectors)",
|
|
191
|
+
self.collectors_by_id.read().keys().len()
|
|
192
|
+
)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
#[derive(Debug)]
|
|
197
|
+
struct PromMetric<T> {
|
|
198
|
+
metric_name: String,
|
|
199
|
+
metric_description: String,
|
|
200
|
+
registry: Registry,
|
|
201
|
+
/// Map from label schema to the corresponding Prometheus vector metric
|
|
202
|
+
vectors: RwLock<HashMap<Vec<String>, T>>,
|
|
203
|
+
/// Bucket configuration for histograms (None for other metric types)
|
|
204
|
+
histogram_buckets: Option<Vec<f64>>,
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
impl<T> PromMetric<T>
|
|
208
|
+
where
|
|
209
|
+
T: Clone + Into<PromCollector> + TryFrom<PromCollector> + 'static,
|
|
210
|
+
{
|
|
211
|
+
fn new(metric_name: String, metric_description: String, registry: Registry) -> Self {
|
|
212
|
+
// Prometheus does not allow dashes
|
|
213
|
+
let metric_name = metric_name.replace('-', "_");
|
|
214
|
+
Self {
|
|
215
|
+
metric_name,
|
|
216
|
+
metric_description,
|
|
217
|
+
registry,
|
|
218
|
+
vectors: RwLock::new(HashMap::new()),
|
|
219
|
+
histogram_buckets: None,
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
fn get_or_create_vector<F>(&self, kvs: &OrderedPromLabelSet, create_fn: F) -> anyhow::Result<T>
|
|
224
|
+
where
|
|
225
|
+
F: FnOnce(&str, &str, &[&str]) -> anyhow::Result<T>,
|
|
226
|
+
{
|
|
227
|
+
// Just the metric label names
|
|
228
|
+
let schema: Vec<String> = kvs.keys_ordered().map(|kv| kv.to_string()).collect();
|
|
229
|
+
|
|
230
|
+
{
|
|
231
|
+
let vectors = self.vectors.read();
|
|
232
|
+
if let Some(vector) = vectors.get(&schema) {
|
|
233
|
+
return Ok(vector.clone());
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
let mut vectors = self.vectors.write();
|
|
238
|
+
// Double-check in case another thread created it
|
|
239
|
+
if let Some(vector) = vectors.get(&schema) {
|
|
240
|
+
return Ok(vector.clone());
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
let description = if self.metric_description.is_empty() {
|
|
244
|
+
&self.metric_name
|
|
245
|
+
} else {
|
|
246
|
+
&self.metric_description
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
let boxed: Box<[&str]> = schema.iter().map(String::as_str).collect();
|
|
250
|
+
let vector = create_fn(&self.metric_name, description, &boxed)?;
|
|
251
|
+
|
|
252
|
+
let maybe_exists = self.registry.register(vector.clone());
|
|
253
|
+
let vector = if let Some(m) = maybe_exists {
|
|
254
|
+
T::try_from(m).map_err(|_| {
|
|
255
|
+
anyhow!(
|
|
256
|
+
"Tried to register a metric that already exists as a different type: {:?}",
|
|
257
|
+
self.metric_name
|
|
258
|
+
)
|
|
259
|
+
})?
|
|
260
|
+
} else {
|
|
261
|
+
vector
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
vectors.insert(schema, vector.clone());
|
|
265
|
+
Ok(vector)
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
impl PromMetric<HistogramVec> {
|
|
270
|
+
fn new_with_buckets(
|
|
271
|
+
metric_name: String,
|
|
272
|
+
metric_description: String,
|
|
273
|
+
registry: Registry,
|
|
274
|
+
buckets: Vec<f64>,
|
|
275
|
+
) -> Self {
|
|
276
|
+
// Prometheus does not allow dashes
|
|
277
|
+
let metric_name = metric_name.replace('-', "_");
|
|
278
|
+
Self {
|
|
279
|
+
metric_name,
|
|
280
|
+
metric_description,
|
|
281
|
+
registry,
|
|
282
|
+
vectors: RwLock::new(HashMap::new()),
|
|
283
|
+
histogram_buckets: Some(buckets),
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
fn get_or_create_vector_with_buckets(
|
|
288
|
+
&self,
|
|
289
|
+
labels: &OrderedPromLabelSet,
|
|
290
|
+
) -> anyhow::Result<HistogramVec> {
|
|
291
|
+
self.get_or_create_vector(labels, |name, desc, label_names| {
|
|
292
|
+
let mut opts = prometheus::HistogramOpts::new(name, desc);
|
|
293
|
+
if let Some(buckets) = &self.histogram_buckets {
|
|
294
|
+
opts = opts.buckets(buckets.clone());
|
|
295
|
+
}
|
|
296
|
+
HistogramVec::new(opts, label_names).map_err(Into::into)
|
|
297
|
+
})
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
static EMPTY_LABEL_SET: OrderedPromLabelSet = OrderedPromLabelSet::new();
|
|
302
|
+
|
|
303
|
+
impl<T> PromMetric<T>
|
|
304
|
+
where
|
|
305
|
+
T: Clone + Collector + 'static,
|
|
306
|
+
{
|
|
307
|
+
fn extract_prometheus_labels<'a>(
|
|
308
|
+
&self,
|
|
309
|
+
attributes: &'a MetricAttributes,
|
|
310
|
+
) -> anyhow::Result<&'a OrderedPromLabelSet, anyhow::Error> {
|
|
311
|
+
if matches!(attributes, MetricAttributes::Empty) {
|
|
312
|
+
return Ok(&EMPTY_LABEL_SET);
|
|
313
|
+
}
|
|
314
|
+
if let MetricAttributes::Prometheus { labels } = attributes {
|
|
315
|
+
Ok(labels)
|
|
316
|
+
} else {
|
|
317
|
+
let e = anyhow!(
|
|
318
|
+
"Must use Prometheus attributes with a Prometheus metric implementation. Got: {:?}",
|
|
319
|
+
attributes
|
|
320
|
+
);
|
|
321
|
+
dbg_panic!("{:?}", e);
|
|
322
|
+
Err(e)
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
fn label_mismatch_err(&self, attributes: &MetricAttributes) -> anyhow::Error {
|
|
327
|
+
let e = anyhow!(
|
|
328
|
+
"Mismatch between expected # of prometheus labels and provided for metric {}. \
|
|
329
|
+
This is an SDK bug. Attributes: {:?}",
|
|
330
|
+
self.metric_name,
|
|
331
|
+
attributes,
|
|
332
|
+
);
|
|
333
|
+
dbg_panic!("{:?}", e);
|
|
334
|
+
e
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
struct CorePromCounter(GenericCounter<prometheus::core::AtomicU64>);
|
|
339
|
+
impl CounterBase for CorePromCounter {
|
|
340
|
+
fn adds(&self, value: u64) {
|
|
341
|
+
self.0.inc_by(value);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
impl MetricAttributable<Box<dyn CounterBase>> for PromMetric<IntCounterVec> {
|
|
345
|
+
fn with_attributes(
|
|
346
|
+
&self,
|
|
347
|
+
attributes: &MetricAttributes,
|
|
348
|
+
) -> Result<Box<dyn CounterBase>, Box<dyn std::error::Error>> {
|
|
349
|
+
let labels = self.extract_prometheus_labels(attributes)?;
|
|
350
|
+
let vector = self.get_or_create_vector(labels, |name, desc, label_names| {
|
|
351
|
+
let opts = Opts::new(name, desc);
|
|
352
|
+
IntCounterVec::new(opts, label_names).map_err(Into::into)
|
|
353
|
+
})?;
|
|
354
|
+
if let Ok(c) = vector.get_metric_with(&labels.as_prom_labels()) {
|
|
355
|
+
Ok(Box::new(CorePromCounter(c)))
|
|
356
|
+
} else {
|
|
357
|
+
Err(self.label_mismatch_err(attributes).into())
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
struct CorePromIntGauge(prometheus::IntGauge);
|
|
363
|
+
impl GaugeBase for CorePromIntGauge {
|
|
364
|
+
fn records(&self, value: u64) {
|
|
365
|
+
self.0.set(value as i64);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
impl MetricAttributable<Box<dyn GaugeBase>> for PromMetric<IntGaugeVec> {
|
|
369
|
+
fn with_attributes(
|
|
370
|
+
&self,
|
|
371
|
+
attributes: &MetricAttributes,
|
|
372
|
+
) -> Result<Box<dyn GaugeBase>, Box<dyn std::error::Error>> {
|
|
373
|
+
let labels = self.extract_prometheus_labels(attributes)?;
|
|
374
|
+
let vector = self.get_or_create_vector(labels, |name, desc, label_names| {
|
|
375
|
+
let opts = Opts::new(name, desc);
|
|
376
|
+
IntGaugeVec::new(opts, label_names).map_err(Into::into)
|
|
377
|
+
})?;
|
|
378
|
+
if let Ok(g) = vector.get_metric_with(&labels.as_prom_labels()) {
|
|
379
|
+
Ok(Box::new(CorePromIntGauge(g)))
|
|
380
|
+
} else {
|
|
381
|
+
Err(self.label_mismatch_err(attributes).into())
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
struct CorePromGauge(prometheus::Gauge);
|
|
387
|
+
impl GaugeF64Base for CorePromGauge {
|
|
388
|
+
fn records(&self, value: f64) {
|
|
389
|
+
self.0.set(value);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
impl MetricAttributable<Box<dyn GaugeF64Base>> for PromMetric<GaugeVec> {
|
|
393
|
+
fn with_attributes(
|
|
394
|
+
&self,
|
|
395
|
+
attributes: &MetricAttributes,
|
|
396
|
+
) -> Result<Box<dyn GaugeF64Base>, Box<dyn std::error::Error>> {
|
|
397
|
+
let labels = self.extract_prometheus_labels(attributes)?;
|
|
398
|
+
let vector = self.get_or_create_vector(labels, |name, desc, label_names| {
|
|
399
|
+
let opts = Opts::new(name, desc);
|
|
400
|
+
GaugeVec::new(opts, label_names).map_err(Into::into)
|
|
401
|
+
})?;
|
|
402
|
+
if let Ok(g) = vector.get_metric_with(&labels.as_prom_labels()) {
|
|
403
|
+
Ok(Box::new(CorePromGauge(g)))
|
|
404
|
+
} else {
|
|
405
|
+
Err(self.label_mismatch_err(attributes).into())
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
#[derive(Clone)]
|
|
411
|
+
struct CorePromHistogram(prometheus::Histogram);
|
|
412
|
+
impl HistogramBase for CorePromHistogram {
|
|
413
|
+
fn records(&self, value: u64) {
|
|
414
|
+
self.0.observe(value as f64);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
impl HistogramF64Base for CorePromHistogram {
|
|
418
|
+
fn records(&self, value: f64) {
|
|
419
|
+
self.0.observe(value);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
#[derive(Debug)]
|
|
424
|
+
struct PromHistogramU64(PromMetric<HistogramVec>);
|
|
425
|
+
impl MetricAttributable<Box<dyn HistogramBase>> for PromHistogramU64 {
|
|
426
|
+
fn with_attributes(
|
|
427
|
+
&self,
|
|
428
|
+
attributes: &MetricAttributes,
|
|
429
|
+
) -> Result<Box<dyn HistogramBase>, Box<dyn std::error::Error>> {
|
|
430
|
+
let labels = self.0.extract_prometheus_labels(attributes)?;
|
|
431
|
+
let vector = self.0.get_or_create_vector_with_buckets(labels)?;
|
|
432
|
+
if let Ok(h) = vector.get_metric_with(&labels.as_prom_labels()) {
|
|
433
|
+
Ok(Box::new(CorePromHistogram(h)))
|
|
434
|
+
} else {
|
|
435
|
+
Err(self.0.label_mismatch_err(attributes).into())
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
#[derive(Debug)]
|
|
441
|
+
struct PromHistogramF64(PromMetric<HistogramVec>);
|
|
442
|
+
impl MetricAttributable<Box<dyn HistogramF64Base>> for PromHistogramF64 {
|
|
443
|
+
fn with_attributes(
|
|
444
|
+
&self,
|
|
445
|
+
attributes: &MetricAttributes,
|
|
446
|
+
) -> Result<Box<dyn HistogramF64Base>, Box<dyn std::error::Error>> {
|
|
447
|
+
let labels = self.0.extract_prometheus_labels(attributes)?;
|
|
448
|
+
let vector = self.0.get_or_create_vector_with_buckets(labels)?;
|
|
449
|
+
if let Ok(h) = vector.get_metric_with(&labels.as_prom_labels()) {
|
|
450
|
+
Ok(Box::new(CorePromHistogram(h)))
|
|
451
|
+
} else {
|
|
452
|
+
Err(self.0.label_mismatch_err(attributes).into())
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/// A CoreMeter implementation backed by Prometheus metrics with dynamic label management
|
|
458
|
+
#[derive(Debug)]
|
|
459
|
+
pub struct CorePrometheusMeter {
|
|
460
|
+
registry: Registry,
|
|
461
|
+
use_seconds_for_durations: bool,
|
|
462
|
+
unit_suffix: bool,
|
|
463
|
+
bucket_overrides: temporal_sdk_core_api::telemetry::HistogramBucketOverrides,
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
impl CorePrometheusMeter {
|
|
467
|
+
pub(super) fn new(
|
|
468
|
+
registry: Registry,
|
|
469
|
+
use_seconds_for_durations: bool,
|
|
470
|
+
unit_suffix: bool,
|
|
471
|
+
bucket_overrides: temporal_sdk_core_api::telemetry::HistogramBucketOverrides,
|
|
472
|
+
) -> Self {
|
|
473
|
+
Self {
|
|
474
|
+
registry,
|
|
475
|
+
use_seconds_for_durations,
|
|
476
|
+
unit_suffix,
|
|
477
|
+
bucket_overrides,
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
fn create_u64_hist(&self, params: &MetricParameters) -> PromHistogramU64 {
|
|
482
|
+
let base_name = params.name.to_string();
|
|
483
|
+
let actual_metric_name = self.get_histogram_metric_name(&base_name, ¶ms.unit);
|
|
484
|
+
let buckets = self.get_buckets_for_metric(&base_name);
|
|
485
|
+
PromHistogramU64(PromMetric::new_with_buckets(
|
|
486
|
+
actual_metric_name,
|
|
487
|
+
params.description.to_string(),
|
|
488
|
+
self.registry.clone(),
|
|
489
|
+
buckets,
|
|
490
|
+
))
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
fn create_f64_hist(&self, params: &MetricParameters) -> PromHistogramF64 {
|
|
494
|
+
let base_name = params.name.to_string();
|
|
495
|
+
let actual_metric_name = self.get_histogram_metric_name(&base_name, ¶ms.unit);
|
|
496
|
+
let buckets = self.get_buckets_for_metric(&base_name);
|
|
497
|
+
PromHistogramF64(PromMetric::new_with_buckets(
|
|
498
|
+
actual_metric_name,
|
|
499
|
+
params.description.to_string(),
|
|
500
|
+
self.registry.clone(),
|
|
501
|
+
buckets,
|
|
502
|
+
))
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
impl CoreMeter for CorePrometheusMeter {
|
|
507
|
+
fn new_attributes(&self, attribs: NewAttributes) -> MetricAttributes {
|
|
508
|
+
MetricAttributes::Prometheus {
|
|
509
|
+
labels: Arc::new(attribs.into()),
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
fn extend_attributes(
|
|
514
|
+
&self,
|
|
515
|
+
existing: MetricAttributes,
|
|
516
|
+
new: NewAttributes,
|
|
517
|
+
) -> MetricAttributes {
|
|
518
|
+
if let MetricAttributes::Prometheus {
|
|
519
|
+
labels: existing_labels,
|
|
520
|
+
} = existing
|
|
521
|
+
{
|
|
522
|
+
let mut all_labels = Arc::unwrap_or_clone(existing_labels);
|
|
523
|
+
for kv in new.attributes.into_iter() {
|
|
524
|
+
all_labels.add_kv(kv);
|
|
525
|
+
}
|
|
526
|
+
MetricAttributes::Prometheus {
|
|
527
|
+
labels: Arc::new(all_labels),
|
|
528
|
+
}
|
|
529
|
+
} else {
|
|
530
|
+
dbg_panic!("Must use Prometheus attributes with a Prometheus metric implementation");
|
|
531
|
+
self.new_attributes(new)
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
fn counter(&self, params: MetricParameters) -> Counter {
|
|
536
|
+
let metric_name = params.name.to_string();
|
|
537
|
+
Counter::new(Arc::new(PromMetric::<IntCounterVec>::new(
|
|
538
|
+
metric_name,
|
|
539
|
+
params.description.to_string(),
|
|
540
|
+
self.registry.clone(),
|
|
541
|
+
)))
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
fn histogram(&self, params: MetricParameters) -> Histogram {
|
|
545
|
+
let hist = self.create_u64_hist(¶ms);
|
|
546
|
+
Histogram::new(Arc::new(hist))
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
fn histogram_f64(&self, params: MetricParameters) -> HistogramF64 {
|
|
550
|
+
let hist = self.create_f64_hist(¶ms);
|
|
551
|
+
HistogramF64::new(Arc::new(hist))
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
fn histogram_duration(&self, mut params: MetricParameters) -> HistogramDuration {
|
|
555
|
+
HistogramDuration::new(Arc::new(if self.use_seconds_for_durations {
|
|
556
|
+
params.unit = "seconds".into();
|
|
557
|
+
DurationHistogram::Seconds(self.create_f64_hist(¶ms))
|
|
558
|
+
} else {
|
|
559
|
+
params.unit = "milliseconds".into();
|
|
560
|
+
DurationHistogram::Milliseconds(self.create_u64_hist(¶ms))
|
|
561
|
+
}))
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
fn gauge(&self, params: MetricParameters) -> Gauge {
|
|
565
|
+
let metric_name = params.name.to_string();
|
|
566
|
+
Gauge::new(Arc::new(PromMetric::<IntGaugeVec>::new(
|
|
567
|
+
metric_name,
|
|
568
|
+
params.description.to_string(),
|
|
569
|
+
self.registry.clone(),
|
|
570
|
+
)))
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
fn gauge_f64(&self, params: MetricParameters) -> GaugeF64 {
|
|
574
|
+
let metric_name = params.name.to_string();
|
|
575
|
+
GaugeF64::new(Arc::new(PromMetric::<GaugeVec>::new(
|
|
576
|
+
metric_name,
|
|
577
|
+
params.description.to_string(),
|
|
578
|
+
self.registry.clone(),
|
|
579
|
+
)))
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
impl CorePrometheusMeter {
|
|
584
|
+
fn get_buckets_for_metric(&self, metric_name: &str) -> Vec<f64> {
|
|
585
|
+
for (name_pattern, buckets) in &self.bucket_overrides.overrides {
|
|
586
|
+
if metric_name.contains(name_pattern) {
|
|
587
|
+
return buckets.clone();
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
let base_metric_name = metric_name.strip_prefix("temporal_").unwrap_or(metric_name);
|
|
591
|
+
default_buckets_for(base_metric_name, self.use_seconds_for_durations).to_vec()
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
fn get_histogram_metric_name(&self, base_name: &str, unit: &str) -> String {
|
|
595
|
+
if self.unit_suffix && !unit.is_empty() {
|
|
596
|
+
format!("{base_name}_{unit}")
|
|
597
|
+
} else {
|
|
598
|
+
base_name.to_string()
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
enum DurationHistogram {
|
|
604
|
+
Milliseconds(PromHistogramU64),
|
|
605
|
+
Seconds(PromHistogramF64),
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
enum DurationHistogramBase {
|
|
609
|
+
Millis(Box<dyn HistogramBase>),
|
|
610
|
+
Secs(Box<dyn HistogramF64Base>),
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
impl HistogramDurationBase for DurationHistogramBase {
|
|
614
|
+
fn records(&self, value: Duration) {
|
|
615
|
+
match self {
|
|
616
|
+
DurationHistogramBase::Millis(h) => h.records(value.as_millis() as u64),
|
|
617
|
+
DurationHistogramBase::Secs(h) => h.records(value.as_secs_f64()),
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
impl MetricAttributable<Box<dyn HistogramDurationBase>> for DurationHistogram {
|
|
622
|
+
fn with_attributes(
|
|
623
|
+
&self,
|
|
624
|
+
attributes: &MetricAttributes,
|
|
625
|
+
) -> Result<Box<dyn HistogramDurationBase>, Box<dyn std::error::Error>> {
|
|
626
|
+
Ok(match self {
|
|
627
|
+
DurationHistogram::Milliseconds(h) => Box::new(DurationHistogramBase::Millis(
|
|
628
|
+
h.with_attributes(attributes)?,
|
|
629
|
+
)),
|
|
630
|
+
DurationHistogram::Seconds(h) => {
|
|
631
|
+
Box::new(DurationHistogramBase::Secs(h.with_attributes(attributes)?))
|
|
632
|
+
}
|
|
633
|
+
})
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
#[cfg(test)]
|
|
638
|
+
mod tests {
|
|
639
|
+
use super::*;
|
|
640
|
+
use crate::telemetry::{TelemetryInstance, metrics::MetricsContext};
|
|
641
|
+
use prometheus::{Encoder, TextEncoder};
|
|
642
|
+
use temporal_sdk_core_api::telemetry::{
|
|
643
|
+
METRIC_PREFIX,
|
|
644
|
+
metrics::{MetricKeyValue, NewAttributes},
|
|
645
|
+
};
|
|
646
|
+
|
|
647
|
+
#[test]
|
|
648
|
+
fn test_prometheus_meter_dynamic_labels() {
|
|
649
|
+
let registry = Registry::new(HashMap::from([("global".to_string(), "value".to_string())]));
|
|
650
|
+
let meter = CorePrometheusMeter::new(
|
|
651
|
+
registry.clone(),
|
|
652
|
+
false,
|
|
653
|
+
false,
|
|
654
|
+
temporal_sdk_core_api::telemetry::HistogramBucketOverrides::default(),
|
|
655
|
+
);
|
|
656
|
+
|
|
657
|
+
let counter = meter.counter(MetricParameters {
|
|
658
|
+
name: "test_counter".into(),
|
|
659
|
+
description: "A test counter metric".into(),
|
|
660
|
+
unit: "".into(),
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
let attrs1 = meter.new_attributes(NewAttributes::new(vec![
|
|
664
|
+
MetricKeyValue::new("service", "service1"),
|
|
665
|
+
MetricKeyValue::new("method", "get"),
|
|
666
|
+
]));
|
|
667
|
+
counter.add(5, &attrs1);
|
|
668
|
+
|
|
669
|
+
let attrs2 = meter.new_attributes(NewAttributes::new(vec![
|
|
670
|
+
MetricKeyValue::new("service", "service2"),
|
|
671
|
+
MetricKeyValue::new("method", "post"),
|
|
672
|
+
]));
|
|
673
|
+
counter.add(3, &attrs2);
|
|
674
|
+
|
|
675
|
+
let output = output_string(®istry);
|
|
676
|
+
|
|
677
|
+
// Both label combinations should be present
|
|
678
|
+
assert!(
|
|
679
|
+
output.contains("test_counter{method=\"get\",service=\"service1\",global=\"value\"} 5")
|
|
680
|
+
);
|
|
681
|
+
assert!(
|
|
682
|
+
output
|
|
683
|
+
.contains("test_counter{method=\"post\",service=\"service2\",global=\"value\"} 3")
|
|
684
|
+
);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
#[test]
|
|
688
|
+
fn test_extend_attributes() {
|
|
689
|
+
let registry = Registry::new(HashMap::new());
|
|
690
|
+
let meter = CorePrometheusMeter::new(
|
|
691
|
+
registry.clone(),
|
|
692
|
+
false,
|
|
693
|
+
false,
|
|
694
|
+
temporal_sdk_core_api::telemetry::HistogramBucketOverrides::default(),
|
|
695
|
+
);
|
|
696
|
+
|
|
697
|
+
let base_attrs = meter.new_attributes(NewAttributes::new(vec![
|
|
698
|
+
MetricKeyValue::new("service", "my_service"),
|
|
699
|
+
MetricKeyValue::new("version", "1.0"),
|
|
700
|
+
]));
|
|
701
|
+
|
|
702
|
+
let extended_attrs = meter.extend_attributes(
|
|
703
|
+
base_attrs,
|
|
704
|
+
NewAttributes::new(vec![
|
|
705
|
+
MetricKeyValue::new("method", "GET"),
|
|
706
|
+
MetricKeyValue::new("version", "2.0"), // This should override
|
|
707
|
+
]),
|
|
708
|
+
);
|
|
709
|
+
|
|
710
|
+
let counter = meter.counter(MetricParameters {
|
|
711
|
+
name: "test_extended".into(),
|
|
712
|
+
description: "Test extended attributes".into(),
|
|
713
|
+
unit: "".into(),
|
|
714
|
+
});
|
|
715
|
+
counter.add(1, &extended_attrs);
|
|
716
|
+
|
|
717
|
+
let output = output_string(®istry);
|
|
718
|
+
|
|
719
|
+
assert!(output.contains("service=\"my_service\""));
|
|
720
|
+
assert!(output.contains("method=\"GET\""));
|
|
721
|
+
assert!(output.contains("version=\"2.0\""));
|
|
722
|
+
assert!(!output.contains("version=\"1.0\""));
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
#[test]
|
|
726
|
+
fn test_workflow_e2e_latency_buckets() {
|
|
727
|
+
let registry = Registry::new(HashMap::new());
|
|
728
|
+
|
|
729
|
+
let meter_ms = CorePrometheusMeter::new(
|
|
730
|
+
registry.clone(),
|
|
731
|
+
false,
|
|
732
|
+
false,
|
|
733
|
+
temporal_sdk_core_api::telemetry::HistogramBucketOverrides::default(),
|
|
734
|
+
);
|
|
735
|
+
|
|
736
|
+
let histogram_ms = meter_ms.histogram_duration(MetricParameters {
|
|
737
|
+
name: format!(
|
|
738
|
+
"temporal_{}",
|
|
739
|
+
crate::telemetry::WORKFLOW_E2E_LATENCY_HISTOGRAM_NAME
|
|
740
|
+
)
|
|
741
|
+
.into(),
|
|
742
|
+
description: "Test workflow e2e latency".into(),
|
|
743
|
+
unit: "duration".into(),
|
|
744
|
+
});
|
|
745
|
+
let attrs = meter_ms.new_attributes(NewAttributes::new(vec![]));
|
|
746
|
+
histogram_ms.record(Duration::from_millis(100), &attrs);
|
|
747
|
+
|
|
748
|
+
let output = output_string(®istry);
|
|
749
|
+
|
|
750
|
+
println!("Milliseconds histogram output:\n{output}");
|
|
751
|
+
|
|
752
|
+
assert!(
|
|
753
|
+
output.contains("le=\"100\""),
|
|
754
|
+
"Missing le=\"100\" bucket in milliseconds output"
|
|
755
|
+
);
|
|
756
|
+
|
|
757
|
+
// Test seconds configuration
|
|
758
|
+
let registry_s = Registry::new(HashMap::new());
|
|
759
|
+
let meter_s = CorePrometheusMeter::new(
|
|
760
|
+
registry_s.clone(),
|
|
761
|
+
true,
|
|
762
|
+
false,
|
|
763
|
+
temporal_sdk_core_api::telemetry::HistogramBucketOverrides::default(),
|
|
764
|
+
);
|
|
765
|
+
|
|
766
|
+
let histogram_s = meter_s.histogram_duration(MetricParameters {
|
|
767
|
+
name: format!(
|
|
768
|
+
"temporal_{}",
|
|
769
|
+
crate::telemetry::WORKFLOW_E2E_LATENCY_HISTOGRAM_NAME
|
|
770
|
+
)
|
|
771
|
+
.into(),
|
|
772
|
+
description: "Test workflow e2e latency".into(),
|
|
773
|
+
unit: "duration".into(),
|
|
774
|
+
});
|
|
775
|
+
let attrs_s = meter_s.new_attributes(NewAttributes::new(vec![]));
|
|
776
|
+
histogram_s.record(Duration::from_millis(100), &attrs_s);
|
|
777
|
+
|
|
778
|
+
let output_s = output_string(®istry_s);
|
|
779
|
+
|
|
780
|
+
println!("Seconds histogram output:\n{output_s}");
|
|
781
|
+
|
|
782
|
+
assert!(
|
|
783
|
+
output_s.contains("le=\"0.1\""),
|
|
784
|
+
"Missing le=\"0.1\" bucket in seconds output"
|
|
785
|
+
);
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
#[test]
|
|
789
|
+
fn can_record_with_no_labels() {
|
|
790
|
+
let registry = Registry::new(HashMap::new());
|
|
791
|
+
let meter = CorePrometheusMeter::new(
|
|
792
|
+
registry.clone(),
|
|
793
|
+
false,
|
|
794
|
+
false,
|
|
795
|
+
temporal_sdk_core_api::telemetry::HistogramBucketOverrides::default(),
|
|
796
|
+
);
|
|
797
|
+
let counter = meter.counter(MetricParameters {
|
|
798
|
+
name: "no_labels".into(),
|
|
799
|
+
description: "No labels".into(),
|
|
800
|
+
unit: "".into(),
|
|
801
|
+
});
|
|
802
|
+
counter.adds(1);
|
|
803
|
+
|
|
804
|
+
let output = output_string(®istry);
|
|
805
|
+
|
|
806
|
+
assert!(output.contains("no_labels 1"));
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
#[test]
|
|
810
|
+
fn works_with_recreated_metrics_context() {
|
|
811
|
+
let registry = Registry::new(HashMap::new());
|
|
812
|
+
let meter = CorePrometheusMeter::new(
|
|
813
|
+
registry.clone(),
|
|
814
|
+
false,
|
|
815
|
+
false,
|
|
816
|
+
temporal_sdk_core_api::telemetry::HistogramBucketOverrides::default(),
|
|
817
|
+
);
|
|
818
|
+
let telem_instance = TelemetryInstance::new(
|
|
819
|
+
None,
|
|
820
|
+
None,
|
|
821
|
+
METRIC_PREFIX.to_string(),
|
|
822
|
+
Some(Arc::new(meter)),
|
|
823
|
+
true,
|
|
824
|
+
);
|
|
825
|
+
let mc = MetricsContext::top_level("foo".to_string(), "q".to_string(), &telem_instance);
|
|
826
|
+
mc.worker_registered();
|
|
827
|
+
drop(mc);
|
|
828
|
+
|
|
829
|
+
let mc = MetricsContext::top_level("foo".to_string(), "q".to_string(), &telem_instance);
|
|
830
|
+
mc.worker_registered();
|
|
831
|
+
|
|
832
|
+
let mc = MetricsContext::top_level("foo".to_string(), "q2".to_string(), &telem_instance);
|
|
833
|
+
mc.worker_registered();
|
|
834
|
+
|
|
835
|
+
let output = output_string(®istry);
|
|
836
|
+
assert!(output.contains("temporal_worker_start{namespace=\"foo\",service_name=\"temporal-core-sdk\",task_queue=\"q\"} 2"));
|
|
837
|
+
assert!(output.contains("temporal_worker_start{namespace=\"foo\",service_name=\"temporal-core-sdk\",task_queue=\"q2\"} 1"));
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
#[test]
|
|
841
|
+
fn metric_name_dashes() {
|
|
842
|
+
let registry = Registry::new(HashMap::new());
|
|
843
|
+
let meter = CorePrometheusMeter::new(
|
|
844
|
+
registry.clone(),
|
|
845
|
+
false,
|
|
846
|
+
false,
|
|
847
|
+
temporal_sdk_core_api::telemetry::HistogramBucketOverrides::default(),
|
|
848
|
+
);
|
|
849
|
+
let dashes = meter.counter(MetricParameters {
|
|
850
|
+
name: "dash-in-name".into(),
|
|
851
|
+
description: "Whatever".into(),
|
|
852
|
+
unit: "".into(),
|
|
853
|
+
});
|
|
854
|
+
dashes.adds(1);
|
|
855
|
+
|
|
856
|
+
let output = output_string(®istry);
|
|
857
|
+
assert!(output.contains("dash_in_name 1"));
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
#[test]
|
|
861
|
+
#[should_panic(expected = "not a valid metric name")]
|
|
862
|
+
fn invalid_metric_name() {
|
|
863
|
+
let registry = Registry::new(HashMap::new());
|
|
864
|
+
let meter = CorePrometheusMeter::new(
|
|
865
|
+
registry.clone(),
|
|
866
|
+
false,
|
|
867
|
+
false,
|
|
868
|
+
temporal_sdk_core_api::telemetry::HistogramBucketOverrides::default(),
|
|
869
|
+
);
|
|
870
|
+
let dashes = meter.counter(MetricParameters {
|
|
871
|
+
name: "not@permitted:symbols".into(),
|
|
872
|
+
description: "Whatever".into(),
|
|
873
|
+
unit: "".into(),
|
|
874
|
+
});
|
|
875
|
+
dashes.adds(1);
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
fn output_string(registry: &Registry) -> String {
|
|
879
|
+
let metric_families = registry.gather();
|
|
880
|
+
let encoder = TextEncoder::new();
|
|
881
|
+
let mut buffer = vec![];
|
|
882
|
+
encoder.encode(&metric_families, &mut buffer).unwrap();
|
|
883
|
+
String::from_utf8(buffer).unwrap()
|
|
884
|
+
}
|
|
885
|
+
}
|