Skip to content

Commit 41d5005

Browse files
Update Core & pass through new metrics options (#1383)
Co-authored-by: James Watkins-Harvey <james.watkinsharvey@temporal.io>
1 parent 553ace5 commit 41d5005

File tree

9 files changed

+409
-263
lines changed

9 files changed

+409
-263
lines changed

packages/core-bridge/Cargo.lock

Lines changed: 293 additions & 226 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/core-bridge/Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ incremental = false
2020
futures = { version = "0.3", features = ["executor"] }
2121
log = "0.4"
2222
neon = { version = "0.10", default-features = false, features = ["napi-6", "event-queue-api"] }
23-
opentelemetry = "0.18"
23+
opentelemetry = "0.22"
2424
parking_lot = "0.12"
25-
prost = "0.11"
26-
prost-types = "0.11"
25+
prost = "0.12"
26+
prost-types = "0.12"
2727
serde_json = "1.0"
2828
tokio = "1.13"
2929
once_cell = "1.19"

packages/core-bridge/sdk-core

Submodule sdk-core updated 63 files

packages/core-bridge/src/conversions.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,17 @@ impl ObjectHandleConversionsExt for Handle<'_, JsObject> {
158158
if let Some(tls_cfg) = tls_cfg {
159159
client_options.tls_cfg(tls_cfg);
160160
}
161+
let headers = match js_optional_getter!(cx, self, "metadata", JsObject) {
162+
None => None,
163+
Some(h) => Some(h.as_hash_map_of_string_to_string(cx).map_err(|reason| {
164+
cx.throw_type_error::<_, HashMap<String, String>>(format!(
165+
"Invalid metadata: {}",
166+
reason
167+
))
168+
.unwrap_err()
169+
})?),
170+
};
171+
client_options.headers(headers);
161172

162173
Ok(client_options
163174
.client_name("temporal-typescript".to_string())
@@ -211,6 +222,22 @@ impl ObjectHandleConversionsExt for Handle<'_, JsObject> {
211222
}
212223
};
213224

225+
if let Some(counters_total_suffix) =
226+
js_optional_value_getter!(cx, prom, "countersTotalSuffix", JsBoolean)
227+
{
228+
options.counters_total_suffix(counters_total_suffix);
229+
}
230+
if let Some(unit_suffix) =
231+
js_optional_value_getter!(cx, prom, "unitSuffix", JsBoolean)
232+
{
233+
options.unit_suffix(unit_suffix);
234+
}
235+
if let Some(use_seconds_for_durations) =
236+
js_optional_value_getter!(cx, prom, "useSecondsForDurations", JsBoolean)
237+
{
238+
options.use_seconds_for_durations(use_seconds_for_durations);
239+
}
240+
214241
let options = options.build().map_err(|e| {
215242
cx.throw_type_error::<_, TelemetryOptions>(format!(
216243
"Failed to build prometheus exporter options: {:?}",
@@ -240,6 +267,12 @@ impl ObjectHandleConversionsExt for Handle<'_, JsObject> {
240267
}
241268
};
242269

270+
if let Some(use_seconds_for_durations) =
271+
js_optional_value_getter!(cx, otel, "useSecondsForDurations", JsBoolean)
272+
{
273+
options.use_seconds_for_durations(use_seconds_for_durations);
274+
}
275+
243276
if let Some(ref headers) = js_optional_getter!(cx, otel, "headers", JsObject) {
244277
options.headers(headers.as_hash_map_of_string_to_string(cx)?);
245278
};

packages/core-bridge/src/runtime.rs

Lines changed: 10 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,23 @@
11
use crate::{conversions::*, errors::*, helpers::*, worker::*};
2-
use neon::context::Context;
3-
use neon::prelude::*;
4-
use parking_lot::RwLock;
5-
use std::cell::Cell;
2+
use neon::{context::Context, prelude::*};
63
use std::{
7-
cell::RefCell,
4+
cell::{Cell, RefCell},
85
collections::HashMap,
96
ops::Deref,
107
sync::Arc,
118
time::{Duration, SystemTime, UNIX_EPOCH},
129
};
1310
use temporal_client::{ClientInitError, ConfiguredClient, TemporalServiceClientWithMetrics};
14-
use temporal_sdk_core::api::telemetry::CoreTelemetry;
15-
use temporal_sdk_core::CoreRuntime;
1611
use temporal_sdk_core::{
12+
api::telemetry::CoreTelemetry,
1713
ephemeral_server::EphemeralServer as CoreEphemeralServer,
1814
init_replay_worker, init_worker,
1915
replay::{HistoryForReplay, ReplayWorkerInput},
20-
ClientOptions, RetryClient, WorkerConfig,
16+
ClientOptions, CoreRuntime, RetryClient, WorkerConfig,
2117
};
22-
use tokio::sync::oneshot;
2318
use tokio::sync::{
2419
mpsc::{channel, unbounded_channel, Sender, UnboundedReceiver, UnboundedSender},
25-
Mutex,
20+
oneshot, Mutex,
2621
};
2722
use tokio_stream::wrappers::ReceiverStream;
2823

@@ -64,7 +59,6 @@ pub enum RuntimeRequest {
6459
CreateClient {
6560
runtime: Arc<RuntimeHandle>,
6661
options: ClientOptions,
67-
headers: Option<HashMap<String, String>>,
6862
/// Used to send the result back into JS
6963
callback: Root<JsFunction>,
7064
},
@@ -166,13 +160,12 @@ pub fn start_bridge_loop(
166160
RuntimeRequest::CreateClient {
167161
runtime,
168162
options,
169-
headers,
170163
callback,
171164
} => {
172165
let mm = core_runtime.telemetry().get_metric_meter();
173166
core_runtime.tokio_handle().spawn(async move {
174167
match options
175-
.connect_no_namespace(mm, headers.map(|h| Arc::new(RwLock::new(h))))
168+
.connect_no_namespace(mm)
176169
.await
177170
{
178171
Err(err) => {
@@ -337,8 +330,7 @@ pub fn start_bridge_loop(
337330
format!("Failed to start test server: {}", err),
338331
)
339332
},
340-
)
341-
.await
333+
).await
342334
});
343335
}
344336
RuntimeRequest::PushReplayHistory {
@@ -350,8 +342,8 @@ pub fn start_bridge_loop(
350342
let sendfut = async move {
351343
tx.send(pushme).await.map_err(|e| {
352344
format!(
353-
"Receive side of history replay channel is gone. This is an sdk bug. {:?}",
354-
e
345+
"Receive side of history replay channel is gone. This is an sdk bug. {:?}",
346+
e
355347
)
356348
})
357349
};
@@ -361,8 +353,7 @@ pub fn start_bridge_loop(
361353
UNEXPECTED_ERROR,
362354
format!("Error pushing replay history {}", err),
363355
)
364-
})
365-
.await
356+
}).await
366357
});
367358
}
368359
}
@@ -453,24 +444,10 @@ pub fn client_new(mut cx: FunctionContext) -> JsResult<JsUndefined> {
453444
let callback = cx.argument::<JsFunction>(2)?;
454445

455446
let client_options = opts.as_client_options(&mut cx)?;
456-
let headers = match js_optional_getter!(&mut cx, &opts, "metadata", JsObject) {
457-
None => None,
458-
Some(h) => Some(
459-
h.as_hash_map_of_string_to_string(&mut cx)
460-
.map_err(|reason| {
461-
cx.throw_type_error::<_, HashMap<String, String>>(format!(
462-
"Invalid metadata: {}",
463-
reason
464-
))
465-
.unwrap_err()
466-
})?,
467-
),
468-
};
469447

470448
let request = RuntimeRequest::CreateClient {
471449
runtime: (**runtime).clone(),
472450
options: client_options,
473-
headers,
474451
callback: callback.root(&mut cx),
475452
};
476453
if let Err(err) = runtime.sender.send(request) {

packages/core-bridge/ts/index.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,10 @@ export interface OtelCollectorExporter {
122122
* @defaults 1 second
123123
*/
124124
metricsExportInterval?: Duration;
125+
/**
126+
* If set to true, the exporter will use seconds for durations instead of milliseconds.
127+
*/
128+
useSecondsForDurations?: boolean;
125129
};
126130
}
127131

@@ -142,6 +146,19 @@ export interface PrometheusMetricsExporter {
142146
* Metrics will be available for scraping under the standard `/metrics` route.
143147
*/
144148
bindAddress: string;
149+
/**
150+
* If set to true, all counter names will include a "_total" suffix.
151+
*/
152+
countersTotalSuffix?: boolean;
153+
/**
154+
* If set to true, all histograms will include the unit in their name as a suffix.
155+
* EX: "_milliseconds"
156+
*/
157+
unitSuffix?: boolean;
158+
/**
159+
* If set to true, the exporter will use seconds for durations instead of milliseconds.
160+
*/
161+
useSecondsForDurations?: boolean;
145162
};
146163
}
147164

packages/test/src/test-native-connection-headers.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ test('NativeConnection passes headers provided in options', async (t) => {
3333
callback: grpc.sendUnaryData<temporal.api.workflowservice.v1.IGetSystemInfoResponse>
3434
) {
3535
const [value] = call.metadata.get('initial');
36+
console.log(call.metadata);
3637
if (value === 'true') {
3738
gotInitialHeader = true;
3839
}

packages/test/src/test-prometheus.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,50 @@ test.serial('Exporting Prometheus metrics from Core works', async (t) => {
6464
await localEnv.teardown();
6565
}
6666
});
67+
68+
test.serial('Exporting Prometheus metrics from Core works with lots of options', async (t) => {
69+
const port = await getRandomPort();
70+
Runtime.install({
71+
telemetryOptions: {
72+
metrics: {
73+
prometheus: {
74+
bindAddress: `127.0.0.1:${port}`,
75+
countersTotalSuffix: true,
76+
unitSuffix: true,
77+
useSecondsForDurations: true,
78+
},
79+
},
80+
},
81+
});
82+
const localEnv = await TestWorkflowEnvironment.createLocal();
83+
try {
84+
const worker = await Worker.create({
85+
connection: localEnv.nativeConnection,
86+
workflowsPath: require.resolve('./workflows'),
87+
taskQueue: 'test-prometheus',
88+
});
89+
const client = new WorkflowClient({
90+
connection: localEnv.connection,
91+
});
92+
await worker.runUntil(async () => {
93+
await client.execute(workflows.successString, {
94+
taskQueue: 'test-prometheus',
95+
workflowId: uuid4(),
96+
});
97+
const resp = await fetch(`http://127.0.0.1:${port}/metrics`);
98+
const text = await resp.text();
99+
// Verify use seconds & unit suffix
100+
t.assert(
101+
text.includes(
102+
'temporal_workflow_task_replay_latency_seconds_bucket{namespace="default",' +
103+
'service_name="temporal-core-sdk",task_queue="test-prometheus",' +
104+
'workflow_type="successString",le="0.001"}'
105+
)
106+
);
107+
// Verify 'total' suffix
108+
t.assert(text.includes('temporal_worker_start_total'));
109+
});
110+
} finally {
111+
await localEnv.teardown();
112+
}
113+
});

packages/worker/src/runtime.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,11 +241,15 @@ export class Runtime {
241241
url: metrics.otel.url,
242242
headers: metrics.otel.headers ?? {},
243243
metricsExportInterval: msToNumber(metrics.otel.metricsExportInterval ?? '1s'),
244+
useSecondsForDurations: metrics.otel.useSecondsForDurations,
244245
},
245246
}
246247
: {
247248
prometheus: {
248249
bindAddress: metrics.prometheus.bindAddress,
250+
unitSuffix: metrics.prometheus.unitSuffix,
251+
countersTotalSuffix: metrics.prometheus.countersTotalSuffix,
252+
useSecondsForDurations: metrics.prometheus.useSecondsForDurations,
249253
},
250254
}),
251255
},

0 commit comments

Comments
 (0)