@temporalio/core-bridge 1.14.1 → 1.15.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.
Files changed (135) hide show
  1. package/Cargo.lock +648 -606
  2. package/bridge-macros/src/derive_tryintojs.rs +40 -0
  3. package/lib/native.d.ts +23 -2
  4. package/package.json +12 -13
  5. package/releases/aarch64-apple-darwin/index.node +0 -0
  6. package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
  7. package/releases/x86_64-apple-darwin/index.node +0 -0
  8. package/releases/x86_64-pc-windows-msvc/index.node +0 -0
  9. package/releases/x86_64-unknown-linux-gnu/index.node +0 -0
  10. package/sdk-core/.cargo/multi-worker-manual-test +0 -0
  11. package/sdk-core/AGENTS.md +2 -2
  12. package/sdk-core/Cargo.toml +1 -1
  13. package/sdk-core/README.md +5 -5
  14. package/sdk-core/crates/client/src/raw.rs +90 -0
  15. package/sdk-core/crates/client/src/worker/mod.rs +103 -28
  16. package/sdk-core/crates/common/Cargo.toml +1 -1
  17. package/sdk-core/crates/common/protos/api_upstream/.github/workflows/create-release.yml +0 -5
  18. package/sdk-core/crates/common/protos/api_upstream/README.md +8 -0
  19. package/sdk-core/crates/common/protos/api_upstream/buf.yaml +3 -0
  20. package/sdk-core/crates/common/protos/api_upstream/openapi/openapiv2.json +2738 -2452
  21. package/sdk-core/crates/common/protos/api_upstream/openapi/openapiv3.yaml +1657 -124
  22. package/sdk-core/crates/common/protos/api_upstream/temporal/api/activity/v1/message.proto +155 -3
  23. package/sdk-core/crates/common/protos/api_upstream/temporal/api/command/v1/message.proto +26 -0
  24. package/sdk-core/crates/common/protos/api_upstream/temporal/api/common/v1/message.proto +8 -1
  25. package/sdk-core/crates/common/protos/api_upstream/temporal/api/deployment/v1/message.proto +26 -0
  26. package/sdk-core/crates/common/protos/api_upstream/temporal/api/enums/v1/activity.proto +81 -0
  27. package/sdk-core/crates/common/protos/api_upstream/temporal/api/enums/v1/event_type.proto +4 -0
  28. package/sdk-core/crates/common/protos/api_upstream/temporal/api/enums/v1/failed_cause.proto +4 -0
  29. package/sdk-core/crates/common/protos/api_upstream/temporal/api/enums/v1/task_queue.proto +15 -0
  30. package/sdk-core/crates/common/protos/api_upstream/temporal/api/enums/v1/workflow.proto +62 -15
  31. package/sdk-core/crates/common/protos/api_upstream/temporal/api/errordetails/v1/message.proto +8 -0
  32. package/sdk-core/crates/common/protos/api_upstream/temporal/api/history/v1/message.proto +107 -17
  33. package/sdk-core/crates/common/protos/api_upstream/temporal/api/namespace/v1/message.proto +15 -0
  34. package/sdk-core/crates/common/protos/api_upstream/temporal/api/nexus/v1/message.proto +4 -0
  35. package/sdk-core/crates/common/protos/api_upstream/temporal/api/operatorservice/v1/request_response.proto +4 -0
  36. package/sdk-core/crates/common/protos/api_upstream/temporal/api/schedule/v1/message.proto +2 -2
  37. package/sdk-core/crates/common/protos/api_upstream/temporal/api/taskqueue/v1/message.proto +2 -0
  38. package/sdk-core/crates/common/protos/api_upstream/temporal/api/worker/v1/message.proto +4 -7
  39. package/sdk-core/crates/common/protos/api_upstream/temporal/api/workflow/v1/message.proto +80 -22
  40. package/sdk-core/crates/common/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +285 -19
  41. package/sdk-core/crates/common/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +154 -10
  42. package/sdk-core/crates/common/protos/local/temporal/sdk/core/core_interface.proto +15 -0
  43. package/sdk-core/crates/common/protos/local/temporal/sdk/core/nexus/nexus.proto +5 -0
  44. package/sdk-core/crates/common/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +4 -0
  45. package/sdk-core/crates/common/protos/local/temporal/sdk/core/workflow_commands/workflow_commands.proto +17 -0
  46. package/sdk-core/crates/common/src/lib.rs +3 -3
  47. package/sdk-core/crates/common/src/protos/canned_histories.rs +16 -0
  48. package/sdk-core/crates/common/src/protos/mod.rs +12 -0
  49. package/sdk-core/crates/common/src/telemetry/metrics.rs +6 -4
  50. package/sdk-core/crates/common/src/telemetry.rs +14 -15
  51. package/sdk-core/crates/common/src/worker.rs +66 -99
  52. package/sdk-core/crates/common/tests/worker_task_types_test.rs +9 -9
  53. package/sdk-core/crates/sdk/src/lib.rs +10 -8
  54. package/sdk-core/crates/sdk/src/workflow_context/options.rs +19 -0
  55. package/sdk-core/crates/sdk-core/Cargo.toml +2 -1
  56. package/sdk-core/crates/sdk-core/benches/workflow_replay_bench.rs +4 -19
  57. package/sdk-core/crates/sdk-core/src/core_tests/mod.rs +9 -6
  58. package/sdk-core/crates/sdk-core/src/core_tests/workers.rs +166 -13
  59. package/sdk-core/crates/sdk-core/src/core_tests/workflow_tasks.rs +42 -33
  60. package/sdk-core/crates/sdk-core/src/ephemeral_server/mod.rs +6 -9
  61. package/sdk-core/crates/sdk-core/src/lib.rs +20 -13
  62. package/sdk-core/crates/sdk-core/src/pollers/poll_buffer.rs +301 -21
  63. package/sdk-core/crates/sdk-core/src/telemetry/log_export.rs +7 -10
  64. package/sdk-core/crates/sdk-core/src/telemetry/metrics.rs +4 -2
  65. package/sdk-core/crates/sdk-core/src/telemetry/mod.rs +2 -3
  66. package/sdk-core/crates/sdk-core/src/test_help/integ_helpers.rs +30 -8
  67. package/sdk-core/crates/sdk-core/src/worker/activities/activity_heartbeat_manager.rs +1 -0
  68. package/sdk-core/crates/sdk-core/src/worker/client/mocks.rs +3 -1
  69. package/sdk-core/crates/sdk-core/src/worker/client.rs +2 -6
  70. package/sdk-core/crates/sdk-core/src/worker/heartbeat.rs +4 -4
  71. package/sdk-core/crates/sdk-core/src/worker/mod.rs +92 -53
  72. package/sdk-core/crates/sdk-core/src/worker/nexus.rs +5 -0
  73. package/sdk-core/crates/sdk-core/src/worker/tuner/resource_based.rs +12 -14
  74. package/sdk-core/crates/sdk-core/src/worker/tuner.rs +36 -36
  75. package/sdk-core/crates/sdk-core/src/worker/workflow/machines/patch_state_machine.rs +5 -8
  76. package/sdk-core/crates/sdk-core/src/worker/workflow/machines/workflow_machines.rs +12 -1
  77. package/sdk-core/crates/sdk-core/src/worker/workflow/managed_run.rs +6 -23
  78. package/sdk-core/crates/sdk-core/src/worker/workflow/mod.rs +46 -3
  79. package/sdk-core/crates/sdk-core/tests/common/mod.rs +45 -45
  80. package/sdk-core/crates/sdk-core/tests/global_metric_tests.rs +7 -10
  81. package/sdk-core/crates/sdk-core/tests/heavy_tests/fuzzy_workflow.rs +3 -5
  82. package/sdk-core/crates/sdk-core/tests/heavy_tests.rs +34 -42
  83. package/sdk-core/crates/sdk-core/tests/integ_tests/ephemeral_server_tests.rs +21 -26
  84. package/sdk-core/crates/sdk-core/tests/integ_tests/heartbeat_tests.rs +1 -0
  85. package/sdk-core/crates/sdk-core/tests/integ_tests/metrics_tests.rs +147 -72
  86. package/sdk-core/crates/sdk-core/tests/integ_tests/polling_tests.rs +27 -48
  87. package/sdk-core/crates/sdk-core/tests/integ_tests/update_tests.rs +5 -15
  88. package/sdk-core/crates/sdk-core/tests/integ_tests/worker_heartbeat_tests.rs +61 -66
  89. package/sdk-core/crates/sdk-core/tests/integ_tests/worker_tests.rs +16 -14
  90. package/sdk-core/crates/sdk-core/tests/integ_tests/worker_versioning_tests.rs +15 -21
  91. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/activities.rs +16 -19
  92. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/cancel_external.rs +1 -3
  93. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs +1 -3
  94. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs +4 -12
  95. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs +14 -9
  96. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/determinism.rs +1 -3
  97. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/eager.rs +2 -6
  98. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +4 -8
  99. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/modify_wf_properties.rs +1 -3
  100. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/nexus.rs +11 -13
  101. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/patches.rs +11 -27
  102. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/resets.rs +3 -5
  103. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/signals.rs +4 -12
  104. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs +7 -13
  105. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/timers.rs +4 -12
  106. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/upsert_search_attrs.rs +1 -3
  107. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests.rs +16 -30
  108. package/sdk-core/crates/sdk-core/tests/main.rs +6 -2
  109. package/sdk-core/crates/sdk-core/tests/manual_tests.rs +40 -49
  110. package/sdk-core/crates/sdk-core/tests/runner.rs +4 -6
  111. package/sdk-core/crates/sdk-core/tests/shared_tests/mod.rs +28 -13
  112. package/sdk-core/crates/sdk-core-c-bridge/Cargo.toml +1 -0
  113. package/sdk-core/crates/sdk-core-c-bridge/include/temporal-sdk-core-c-bridge.h +24 -13
  114. package/sdk-core/crates/sdk-core-c-bridge/src/client.rs +103 -19
  115. package/sdk-core/crates/sdk-core-c-bridge/src/lib.rs +89 -5
  116. package/sdk-core/crates/sdk-core-c-bridge/src/metric.rs +1 -2
  117. package/sdk-core/crates/sdk-core-c-bridge/src/runtime.rs +59 -66
  118. package/sdk-core/crates/sdk-core-c-bridge/src/testing.rs +10 -10
  119. package/sdk-core/crates/sdk-core-c-bridge/src/tests/context.rs +46 -11
  120. package/sdk-core/crates/sdk-core-c-bridge/src/tests/mod.rs +103 -7
  121. package/sdk-core/crates/sdk-core-c-bridge/src/tests/utils.rs +6 -48
  122. package/sdk-core/crates/sdk-core-c-bridge/src/worker.rs +13 -17
  123. package/sdk-core/docker-cgroup-tests.sh +0 -0
  124. package/sdk-core/etc/cargo-tokio-console.sh +0 -0
  125. package/sdk-core/etc/integ-with-otel.sh +0 -0
  126. package/sdk-core/etc/regen-depgraph.sh +0 -0
  127. package/src/client.rs +30 -0
  128. package/src/helpers/try_into_js.rs +88 -2
  129. package/src/metrics.rs +272 -22
  130. package/src/runtime.rs +91 -41
  131. package/src/testing.rs +9 -16
  132. package/src/worker.rs +76 -55
  133. package/ts/native.ts +38 -2
  134. package/LICENSE +0 -21
  135. package/sdk-core/crates/macros/LICENSE.txt +0 -21
package/src/metrics.rs CHANGED
@@ -1,28 +1,39 @@
1
+ use std::any::Any;
1
2
  use std::collections::HashMap;
3
+ use std::sync::Arc;
2
4
 
3
- use anyhow::Context as _;
4
5
  use neon::prelude::*;
5
6
  use serde::Deserialize;
6
7
 
7
8
  use temporalio_common::telemetry::metrics::{
8
- CoreMeter, Counter as CoreCounter, Gauge as CoreGauge, Histogram as CoreHistogram,
9
- MetricParametersBuilder, NewAttributes, TemporalMeter,
9
+ BufferInstrumentRef as CoreBufferInstrumentRef, CoreMeter, Counter as CoreCounter,
10
+ CustomMetricAttributes, Gauge as CoreGauge, Histogram as CoreHistogram, MetricCallBufferer,
11
+ MetricEvent as CoreMetricEvent, MetricKind as CoreMetricKind,
12
+ MetricParameters as CoreMetricParameters, NewAttributes, TemporalMeter,
10
13
  };
11
14
  use temporalio_common::telemetry::metrics::{
12
15
  GaugeF64 as CoreGaugeF64, HistogramF64 as CoreHistogramF64,
13
16
  };
14
- use temporalio_common::telemetry::metrics::{
15
- MetricKeyValue as CoreMetricKeyValue, MetricValue as CoreMetricValue,
17
+ use temporalio_common::telemetry::{
18
+ metrics,
19
+ metrics::{MetricKeyValue as CoreMetricKeyValue, MetricValue as CoreMetricValue},
16
20
  };
17
21
 
18
- use bridge_macros::js_function;
22
+ use bridge_macros::{TryIntoJs, js_function};
23
+ use temporalio_sdk_core::telemetry::MetricsCallBuffer as CoreMetricsCallBuffer;
19
24
 
25
+ use crate::helpers::properties::ObjectExt as _;
26
+ use crate::helpers::try_into_js::{MemoizedHandle, OptionAsUndefined};
20
27
  use crate::helpers::{
21
28
  BridgeError, BridgeResult, JsonString, MutableFinalize, OpaqueInboundHandle,
22
- OpaqueOutboundHandle,
29
+ OpaqueOutboundHandle, TryIntoJs,
23
30
  };
24
31
  use crate::runtime::Runtime;
25
32
 
33
+ ////////////////////////////////////////////////////////////////////////////////////////////////////
34
+ // Metric Meter (aka Custom Metrics)
35
+ ////////////////////////////////////////////////////////////////////////////////////////////////////
36
+
26
37
  pub fn init(cx: &mut neon::prelude::ModuleContext) -> neon::prelude::NeonResult<()> {
27
38
  cx.export_function("newMetricCounter", new_metric_counter)?;
28
39
  cx.export_function("newMetricHistogram", new_metric_histogram)?;
@@ -116,12 +127,11 @@ pub fn new_metric_counter(
116
127
  ))?;
117
128
 
118
129
  let counter = meter.inner.counter(
119
- MetricParametersBuilder::default()
130
+ CoreMetricParameters::builder()
120
131
  .name(name)
121
132
  .unit(unit)
122
133
  .description(description)
123
- .build()
124
- .context("Failed to build metric parameters")?,
134
+ .build(),
125
135
  );
126
136
 
127
137
  Ok(OpaqueOutboundHandle::new(Counter { meter, counter }))
@@ -143,12 +153,11 @@ pub fn new_metric_histogram(
143
153
  ))?;
144
154
 
145
155
  let histogram = meter.inner.histogram(
146
- MetricParametersBuilder::default()
156
+ CoreMetricParameters::builder()
147
157
  .name(name)
148
158
  .unit(unit)
149
159
  .description(description)
150
- .build()
151
- .context("Failed to build metric parameters")?,
160
+ .build(),
152
161
  );
153
162
 
154
163
  Ok(OpaqueOutboundHandle::new(Histogram { meter, histogram }))
@@ -170,12 +179,11 @@ pub fn new_metric_histogram_f64(
170
179
  ))?;
171
180
 
172
181
  let histogram = meter.inner.histogram_f64(
173
- MetricParametersBuilder::default()
182
+ CoreMetricParameters::builder()
174
183
  .name(name)
175
184
  .unit(unit)
176
185
  .description(description)
177
- .build()
178
- .context("Failed to build metric parameters")?,
186
+ .build(),
179
187
  );
180
188
 
181
189
  Ok(OpaqueOutboundHandle::new(HistogramF64 { meter, histogram }))
@@ -197,12 +205,11 @@ pub fn new_metric_gauge(
197
205
  ))?;
198
206
 
199
207
  let gauge = meter.inner.gauge(
200
- MetricParametersBuilder::default()
208
+ CoreMetricParameters::builder()
201
209
  .name(name)
202
210
  .unit(unit)
203
211
  .description(description)
204
- .build()
205
- .context("Failed to build metric parameters")?,
212
+ .build(),
206
213
  );
207
214
 
208
215
  Ok(OpaqueOutboundHandle::new(Gauge { meter, gauge }))
@@ -224,12 +231,11 @@ pub fn new_metric_gauge_f64(
224
231
  ))?;
225
232
 
226
233
  let gauge = meter.inner.gauge_f64(
227
- MetricParametersBuilder::default()
234
+ CoreMetricParameters::builder()
228
235
  .name(name)
229
236
  .unit(unit)
230
237
  .description(description)
231
- .build()
232
- .context("Failed to build metric parameters")?,
238
+ .build(),
233
239
  );
234
240
 
235
241
  Ok(OpaqueOutboundHandle::new(GaugeF64 { meter, gauge }))
@@ -245,10 +251,12 @@ pub fn add_metric_counter_value(
245
251
  attributes: JsonString<MetricAttributes>,
246
252
  ) -> BridgeResult<()> {
247
253
  let counter_handle = counter_handle.borrow()?;
254
+
248
255
  let attributes = counter_handle
249
256
  .meter
250
257
  .inner
251
258
  .new_attributes(parse_metric_attributes(attributes.value));
259
+
252
260
  counter_handle.counter.add(value as u64, &attributes);
253
261
  Ok(())
254
262
  }
@@ -313,6 +321,235 @@ pub fn set_metric_gauge_f64_value(
313
321
  Ok(())
314
322
  }
315
323
 
324
+ ////////////////////////////////////////////////////////////////////////////////////////////////////
325
+ // Buffered Metrics (aka lang-side metrics exporter)
326
+ ////////////////////////////////////////////////////////////////////////////////////////////////////
327
+
328
+ #[derive(Debug)]
329
+ pub struct MetricsCallBuffer {
330
+ pub(crate) core_buffer: Arc<CoreMetricsCallBuffer<BufferedMetricRef>>,
331
+ use_seconds_for_durations: bool,
332
+ }
333
+
334
+ impl MetricsCallBuffer {
335
+ pub(crate) fn new(max_buffer_size: usize, use_seconds_for_durations: bool) -> Self {
336
+ Self {
337
+ core_buffer: Arc::new(CoreMetricsCallBuffer::new(max_buffer_size)),
338
+ use_seconds_for_durations,
339
+ }
340
+ }
341
+
342
+ pub(crate) fn retrieve(&self) -> Vec<BufferedMetricUpdate> {
343
+ self.core_buffer
344
+ .retrieve()
345
+ .iter()
346
+ .filter_map(|e| self.convert_metric_event(e))
347
+ .collect()
348
+ }
349
+
350
+ fn convert_metric_event(
351
+ &self,
352
+ event: &CoreMetricEvent<BufferedMetricRef>,
353
+ ) -> Option<BufferedMetricUpdate> {
354
+ match event {
355
+ CoreMetricEvent::Create {
356
+ params,
357
+ populate_into,
358
+ kind,
359
+ } => {
360
+ // Create the metric and put it on the lazy ref
361
+ let metric = BufferedMetric::new(params, *kind, self.use_seconds_for_durations);
362
+ populate_into
363
+ .set(Arc::new(BufferedMetricRef(MemoizedHandle::new(metric))))
364
+ .expect("Unable to set buffered metric on reference");
365
+
366
+ None
367
+ }
368
+
369
+ // Create the attributes and put it on the lazy ref
370
+ CoreMetricEvent::CreateAttributes {
371
+ populate_into,
372
+ append_from,
373
+ attributes,
374
+ } => {
375
+ let append_from = append_from.as_ref().map(|f| {
376
+ f.get()
377
+ .clone()
378
+ .as_any()
379
+ .downcast::<BufferedMetricAttributesRef>()
380
+ .expect("Unable to downcast to expected buffered metric attributes")
381
+ });
382
+ let attributes = BufferedMetricAttributes {
383
+ new_attributes: attributes.clone(),
384
+ append_from: append_from.map(|f| f.as_ref().clone()),
385
+ };
386
+
387
+ let r = BufferedMetricAttributesRef(MemoizedHandle::new(attributes));
388
+ populate_into
389
+ .set(Arc::new(r))
390
+ .expect("Unable to set buffered metric attributes on reference");
391
+
392
+ None
393
+ }
394
+
395
+ CoreMetricEvent::Update {
396
+ instrument,
397
+ attributes,
398
+ update,
399
+ } => Some(BufferedMetricUpdate {
400
+ metric: instrument.get().as_ref().clone(),
401
+ #[allow(clippy::match_same_arms, clippy::cast_precision_loss)]
402
+ value: match update {
403
+ metrics::MetricUpdateVal::Duration(v) if self.use_seconds_for_durations => {
404
+ v.as_secs_f64()
405
+ }
406
+ metrics::MetricUpdateVal::Duration(v) => v.as_millis() as f64,
407
+ metrics::MetricUpdateVal::Delta(v) => *v as f64,
408
+ metrics::MetricUpdateVal::DeltaF64(v) => *v,
409
+ metrics::MetricUpdateVal::Value(v) => *v as f64,
410
+ metrics::MetricUpdateVal::ValueF64(v) => *v,
411
+ },
412
+ attributes: attributes
413
+ .get()
414
+ .clone()
415
+ .as_any()
416
+ .downcast::<BufferedMetricAttributesRef>()
417
+ .expect("Unable to downcast to expected buffered metric attributes")
418
+ .as_ref()
419
+ .clone(),
420
+ }),
421
+ }
422
+ }
423
+ }
424
+
425
+ #[derive(TryIntoJs)]
426
+ pub struct BufferedMetricUpdate {
427
+ metric: BufferedMetricRef,
428
+ value: f64,
429
+ attributes: BufferedMetricAttributesRef,
430
+ }
431
+
432
+ #[derive(TryIntoJs, Clone, Debug)]
433
+ struct BufferedMetric {
434
+ name: String,
435
+ description: OptionAsUndefined<String>,
436
+ unit: OptionAsUndefined<String>,
437
+ kind: MetricKind,
438
+ value_type: MetricValueType,
439
+ }
440
+
441
+ impl BufferedMetric {
442
+ pub fn new(
443
+ params: &CoreMetricParameters,
444
+ kind: CoreMetricKind,
445
+ use_seconds_for_durations: bool,
446
+ ) -> Self {
447
+ let unit = match kind {
448
+ CoreMetricKind::HistogramDuration if params.unit == "duration" => {
449
+ Some((if use_seconds_for_durations { "s" } else { "ms" }).to_string())
450
+ }
451
+ _ => (!params.unit.is_empty()).then_some(params.unit.to_string()),
452
+ };
453
+
454
+ #[allow(clippy::match_same_arms)]
455
+ let (kind, value_type) = match kind {
456
+ CoreMetricKind::Counter => (MetricKind::Counter, MetricValueType::Int),
457
+ CoreMetricKind::Gauge => (MetricKind::Gauge, MetricValueType::Int),
458
+ CoreMetricKind::GaugeF64 => (MetricKind::Gauge, MetricValueType::Float),
459
+ CoreMetricKind::Histogram => (MetricKind::Histogram, MetricValueType::Int),
460
+ CoreMetricKind::HistogramF64 => (MetricKind::Histogram, MetricValueType::Float),
461
+ CoreMetricKind::HistogramDuration => (MetricKind::Histogram, MetricValueType::Int),
462
+ };
463
+
464
+ let description =
465
+ (!params.description.is_empty()).then_some(params.description.to_string());
466
+
467
+ Self {
468
+ name: params.name.to_string(),
469
+ description: description.into(),
470
+ unit: unit.into(),
471
+ kind,
472
+ value_type,
473
+ }
474
+ }
475
+ }
476
+
477
+ #[derive(Clone, Debug)]
478
+ pub struct BufferedMetricRef(MemoizedHandle<BufferedMetric>);
479
+ impl CoreBufferInstrumentRef for BufferedMetricRef {}
480
+
481
+ impl TryIntoJs for BufferedMetricRef {
482
+ type Output = JsObject;
483
+ fn try_into_js<'cx>(self, cx: &mut impl Context<'cx>) -> JsResult<'cx, Self::Output> {
484
+ self.0.try_into_js(cx)
485
+ }
486
+ }
487
+
488
+ #[derive(Clone, Debug)]
489
+ struct BufferedMetricAttributes {
490
+ new_attributes: Vec<CoreMetricKeyValue>,
491
+ append_from: Option<BufferedMetricAttributesRef>,
492
+ }
493
+
494
+ impl TryIntoJs for BufferedMetricAttributes {
495
+ type Output = JsObject;
496
+ fn try_into_js<'cx>(self, cx: &mut impl Context<'cx>) -> JsResult<'cx, Self::Output> {
497
+ let object = cx.empty_object();
498
+
499
+ // Copy existing attributes, if any
500
+ if let Some(existing) = self.append_from {
501
+ let existing_attrs = existing.try_into_js(cx)?;
502
+
503
+ object_assign(cx, object, existing_attrs)?;
504
+ }
505
+
506
+ // Assign new attributes
507
+ for kv in self.new_attributes {
508
+ let k = kv.key.as_str();
509
+ #[allow(clippy::cast_precision_loss)]
510
+ match &kv.value {
511
+ metrics::MetricValue::String(v) => object.set_property_from(cx, k, v.as_str()),
512
+ metrics::MetricValue::Int(v) => object.set_property_from(cx, k, *v as f64),
513
+ metrics::MetricValue::Float(v) => object.set_property_from(cx, k, *v),
514
+ metrics::MetricValue::Bool(v) => object.set_property_from(cx, k, *v),
515
+ }?;
516
+ }
517
+
518
+ Ok(object)
519
+ }
520
+ }
521
+
522
+ #[derive(Clone, Debug)]
523
+ struct BufferedMetricAttributesRef(MemoizedHandle<BufferedMetricAttributes>);
524
+
525
+ impl CustomMetricAttributes for BufferedMetricAttributesRef {
526
+ fn as_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync> {
527
+ self as Arc<dyn Any + Send + Sync>
528
+ }
529
+ }
530
+
531
+ impl TryIntoJs for BufferedMetricAttributesRef {
532
+ type Output = JsObject;
533
+ fn try_into_js<'cx>(self, cx: &mut impl Context<'cx>) -> JsResult<'cx, Self::Output> {
534
+ self.0.try_into_js(cx)
535
+ }
536
+ }
537
+
538
+ #[derive(TryIntoJs, Clone, Debug)]
539
+ enum MetricKind {
540
+ Counter,
541
+ Gauge,
542
+ Histogram,
543
+ }
544
+
545
+ #[derive(TryIntoJs, Clone, Debug)]
546
+ enum MetricValueType {
547
+ Int,
548
+ Float,
549
+ }
550
+
551
+ ////////////////////////////////////////////////////////////////////////////////////////////////////
552
+ // Helpers
316
553
  ////////////////////////////////////////////////////////////////////////////////////////////////////
317
554
 
318
555
  fn parse_metric_attributes(attrs: MetricAttributes) -> NewAttributes {
@@ -326,3 +563,16 @@ fn parse_metric_attributes(attrs: MetricAttributes) -> NewAttributes {
326
563
  .collect();
327
564
  NewAttributes { attributes: attrs }
328
565
  }
566
+
567
+ fn object_assign<'cx>(
568
+ cx: &mut impl Context<'cx>,
569
+ object: Handle<'cx, JsObject>,
570
+ source: Handle<'cx, JsObject>,
571
+ ) -> JsResult<'cx, JsObject> {
572
+ let object_class = cx.global::<JsFunction>("Object")?;
573
+ let assign_function = object_class.get::<JsFunction, _, _>(cx, "assign")?;
574
+ let null = cx.null();
575
+ assign_function.call(cx, null, vec![object.upcast(), source.upcast()])?;
576
+
577
+ Ok(object)
578
+ }
package/src/runtime.rs CHANGED
@@ -10,7 +10,7 @@ use temporalio_common::telemetry::{
10
10
  PrometheusExporterOptions as CorePrometheusExporterOptions, metrics::CoreMeter,
11
11
  };
12
12
  use temporalio_sdk_core::{
13
- CoreRuntime, RuntimeOptionsBuilder, TokioRuntimeBuilder,
13
+ CoreRuntime, TokioRuntimeBuilder,
14
14
  telemetry::{build_otlp_metric_exporter, start_prometheus_metric_exporter},
15
15
  };
16
16
 
@@ -20,6 +20,7 @@ use tokio_stream::StreamExt as _;
20
20
  use crate::{
21
21
  helpers::{handles::MutableFinalize, *},
22
22
  logs::LogEntry,
23
+ metrics::{BufferedMetricUpdate, MetricsCallBuffer},
23
24
  };
24
25
 
25
26
  #[macro_export]
@@ -36,6 +37,10 @@ macro_rules! enter_sync {
36
37
  pub fn init(cx: &mut neon::prelude::ModuleContext) -> neon::prelude::NeonResult<()> {
37
38
  cx.export_function("newRuntime", runtime_new)?;
38
39
  cx.export_function("runtimeShutdown", runtime_shutdown)?;
40
+ cx.export_function(
41
+ "runtimeRetrieveBufferedMetrics",
42
+ runtime_retrieve_buffered_metrics,
43
+ )?;
39
44
 
40
45
  Ok(())
41
46
  }
@@ -51,6 +56,9 @@ pub struct Runtime {
51
56
  // For some unknown reason, the otel metrics exporter will go crazy on shutdown in some
52
57
  // scenarios if we don't hold on to the `CoreOtelMeter` till the `Runtime` finally gets dropped.
53
58
  _otel_metrics_exporter: Option<Arc<dyn CoreMeter + 'static>>,
59
+
60
+ // Buffered metrics call buffer, if buffered metrics are enabled
61
+ pub(crate) metrics_call_buffer: Option<MetricsCallBuffer>,
54
62
  }
55
63
 
56
64
  /// Initialize Core global telemetry and create the tokio runtime required to run Core.
@@ -63,41 +71,58 @@ pub fn runtime_new(
63
71
  bridge_options.try_into()?;
64
72
 
65
73
  // Create core runtime which starts tokio multi-thread runtime
66
- let runtime_options = RuntimeOptionsBuilder::default()
74
+ let runtime_options = temporalio_sdk_core::RuntimeOptions::builder()
67
75
  .telemetry_options(telemetry_options)
68
76
  .heartbeat_interval(worker_heartbeat_interval_millis.map(Duration::from_millis))
69
77
  .build()
70
- .context("Failed to build runtime options")?;
78
+ .map_err(|err| BridgeError::TypeError {
79
+ message: format!("Failed to build runtime options: {err}"),
80
+ field: None,
81
+ })?;
71
82
  let mut core_runtime = CoreRuntime::new(runtime_options, TokioRuntimeBuilder::default())
72
83
  .context("Failed to initialize Core Runtime")?;
73
84
 
74
85
  enter_sync!(core_runtime);
75
86
 
76
87
  // Run the metrics exporter task, if needed. Created after Runtime since it needs Tokio handle
77
- let (prom_metrics_exporter_task, otel_metrics_exporter) = match metrics_options {
78
- Some(BridgeMetricsExporter::Prometheus(prom_opts)) => {
79
- let exporter = start_prometheus_metric_exporter(prom_opts)
80
- .context("Failed to start prometheus metrics exporter")?;
88
+ let (prom_metrics_exporter_task, otel_metrics_exporter, metrics_call_buffer) =
89
+ match metrics_options {
90
+ Some(BridgeMetricsExporter::Prometheus(prom_opts)) => {
91
+ let exporter = start_prometheus_metric_exporter(prom_opts)
92
+ .context("Failed to start prometheus metrics exporter")?;
81
93
 
82
- core_runtime
83
- .telemetry_mut()
84
- .attach_late_init_metrics(exporter.meter);
94
+ core_runtime
95
+ .telemetry_mut()
96
+ .attach_late_init_metrics(exporter.meter);
85
97
 
86
- (Some(exporter.abort_handle), None)
87
- }
88
- Some(BridgeMetricsExporter::Otel(otel_opts)) => {
89
- let exporter = build_otlp_metric_exporter(otel_opts)
90
- .context("Failed to start OTel metrics exporter")?;
98
+ (Some(exporter.abort_handle), None, None)
99
+ }
100
+ Some(BridgeMetricsExporter::Otel(otel_opts)) => {
101
+ let exporter = build_otlp_metric_exporter(otel_opts)
102
+ .context("Failed to start OTel metrics exporter")?;
91
103
 
92
- let exporter: Arc<dyn CoreMeter + 'static> = Arc::new(exporter);
93
- core_runtime
94
- .telemetry_mut()
95
- .attach_late_init_metrics(exporter.clone());
104
+ let exporter: Arc<dyn CoreMeter + 'static> = Arc::new(exporter);
105
+ core_runtime
106
+ .telemetry_mut()
107
+ .attach_late_init_metrics(exporter.clone());
96
108
 
97
- (None, Some(exporter))
98
- }
99
- None => (None, None),
100
- };
109
+ (None, Some(exporter), None)
110
+ }
111
+ Some(BridgeMetricsExporter::Buffer {
112
+ max_buffer_size,
113
+ use_seconds_for_durations,
114
+ }) => {
115
+ let metrics_call_buffer =
116
+ MetricsCallBuffer::new(max_buffer_size, use_seconds_for_durations);
117
+
118
+ core_runtime
119
+ .telemetry_mut()
120
+ .attach_late_init_metrics(metrics_call_buffer.core_buffer.clone());
121
+
122
+ (None, None, Some(metrics_call_buffer))
123
+ }
124
+ None => (None, None, None),
125
+ };
101
126
 
102
127
  // Run the log exporter task, if needed. Created after Runtime since it needs Tokio handle.
103
128
  let log_exporter_task = if let BridgeLogExporter::Push { stream, receiver } = logging_options {
@@ -127,6 +152,7 @@ pub fn runtime_new(
127
152
  log_exporter_task,
128
153
  metrics_exporter_task: prom_metrics_exporter_task.map(Arc::new),
129
154
  _otel_metrics_exporter: otel_metrics_exporter,
155
+ metrics_call_buffer,
130
156
  }))
131
157
  }
132
158
 
@@ -140,6 +166,24 @@ pub fn runtime_shutdown(runtime: OpaqueInboundHandle<Runtime>) -> BridgeResult<(
140
166
  Ok(())
141
167
  }
142
168
 
169
+ /// Retrieve buffered metrics from the runtime.
170
+ ///
171
+ /// This function drains the metrics buffer and returns all metric updates that have been
172
+ /// accumulated since the last call to this function.
173
+ #[js_function]
174
+ pub fn runtime_retrieve_buffered_metrics(
175
+ runtime: OpaqueInboundHandle<Runtime>,
176
+ ) -> BridgeResult<Vec<BufferedMetricUpdate>> {
177
+ let runtime = runtime.borrow()?;
178
+ let buffer = runtime.metrics_call_buffer.as_ref().ok_or_else(|| {
179
+ BridgeError::UnexpectedError(
180
+ "Attempting to retrieve buffered metrics of a runtime without buffer".into(),
181
+ )
182
+ })?;
183
+
184
+ Ok(buffer.retrieve())
185
+ }
186
+
143
187
  /// Drop will handle the cleanup
144
188
  impl MutableFinalize for Runtime {}
145
189
 
@@ -223,6 +267,10 @@ impl RuntimeExt for Arc<CoreRuntime> {
223
267
  pub enum BridgeMetricsExporter {
224
268
  Prometheus(CorePrometheusExporterOptions),
225
269
  Otel(CoreOtelCollectorOptions),
270
+ Buffer {
271
+ max_buffer_size: usize,
272
+ use_seconds_for_durations: bool,
273
+ },
226
274
  }
227
275
 
228
276
  pub enum BridgeLogExporter {
@@ -238,15 +286,12 @@ pub enum BridgeLogExporter {
238
286
  mod config {
239
287
  use std::{collections::HashMap, net::SocketAddr, sync::Arc, time::Duration};
240
288
 
241
- use anyhow::Context as _;
242
-
243
289
  use neon::prelude::*;
244
290
  use temporalio_common::telemetry::{
245
291
  HistogramBucketOverrides, Logger as CoreTelemetryLogger, MetricTemporality,
246
- OtelCollectorOptions as CoreOtelCollectorOptions, OtelCollectorOptionsBuilder,
247
- OtlpProtocol, PrometheusExporterOptions as CorePrometheusExporterOptions,
248
- PrometheusExporterOptionsBuilder, TelemetryOptions as CoreTelemetryOptions,
249
- TelemetryOptionsBuilder,
292
+ OtelCollectorOptions as CoreOtelCollectorOptions, OtlpProtocol,
293
+ PrometheusExporterOptions as CorePrometheusExporterOptions,
294
+ TelemetryOptions as CoreTelemetryOptions,
250
295
  };
251
296
  use temporalio_sdk_core::{Url, telemetry::CoreLogStreamConsumer};
252
297
 
@@ -291,6 +336,7 @@ mod config {
291
336
  pub(super) enum MetricsExporterOptions {
292
337
  Prometheus(PrometheusMetricsExporterConfig),
293
338
  Otel(OtelMetricsExporterConfig),
339
+ Buffer(BufferedMetricsExporterConfig),
294
340
  }
295
341
 
296
342
  #[derive(Debug, Clone, TryFromJs)]
@@ -315,6 +361,12 @@ mod config {
315
361
  protocol: StringEncoded<OtlpProtocol>,
316
362
  }
317
363
 
364
+ #[derive(Debug, Clone, TryFromJs)]
365
+ pub(super) struct BufferedMetricsExporterConfig {
366
+ max_buffer_size: usize,
367
+ use_seconds_for_durations: bool,
368
+ }
369
+
318
370
  /// A private newtype so that we can implement `TryFromJs` on simple externally defined enums
319
371
  #[derive(Debug, Clone)]
320
372
  struct StringEncoded<T>(T);
@@ -360,13 +412,11 @@ mod config {
360
412
  }
361
413
  };
362
414
 
363
- let mut telemetry_options = TelemetryOptionsBuilder::default();
364
- let telemetry_options = telemetry_options
415
+ let telemetry_options = CoreTelemetryOptions::builder()
365
416
  .logging(telemetry_logger)
366
417
  .metric_prefix(telemetry.metric_prefix)
367
418
  .attach_service_name(telemetry.attach_service_name)
368
- .build()
369
- .context("Failed to build telemetry options")?;
419
+ .build();
370
420
 
371
421
  let metrics_exporter = metrics_exporter
372
422
  .map(std::convert::TryInto::try_into)
@@ -389,6 +439,10 @@ mod config {
389
439
  Ok(super::BridgeMetricsExporter::Prometheus(prom.try_into()?))
390
440
  }
391
441
  Self::Otel(otel) => Ok(super::BridgeMetricsExporter::Otel(otel.try_into()?)),
442
+ Self::Buffer(buffered) => Ok(super::BridgeMetricsExporter::Buffer {
443
+ max_buffer_size: buffered.max_buffer_size,
444
+ use_seconds_for_durations: buffered.use_seconds_for_durations,
445
+ }),
392
446
  }
393
447
  }
394
448
  }
@@ -397,8 +451,7 @@ mod config {
397
451
  type Error = BridgeError;
398
452
 
399
453
  fn try_into(self) -> BridgeResult<CorePrometheusExporterOptions> {
400
- let mut options = PrometheusExporterOptionsBuilder::default();
401
- let options = options
454
+ let options = CorePrometheusExporterOptions::builder()
402
455
  .socket_addr(self.socket_addr)
403
456
  .counters_total_suffix(self.counters_total_suffix)
404
457
  .unit_suffix(self.unit_suffix)
@@ -407,8 +460,7 @@ mod config {
407
460
  overrides: self.histogram_bucket_overrides,
408
461
  })
409
462
  .global_tags(self.global_tags)
410
- .build()
411
- .context("Failed to build prometheus exporter options")?;
463
+ .build();
412
464
 
413
465
  Ok(options)
414
466
  }
@@ -418,8 +470,7 @@ mod config {
418
470
  type Error = BridgeError;
419
471
 
420
472
  fn try_into(self) -> BridgeResult<CoreOtelCollectorOptions> {
421
- let mut options = OtelCollectorOptionsBuilder::default();
422
- let options = options
473
+ let options = CoreOtelCollectorOptions::builder()
423
474
  .url(self.url)
424
475
  .protocol(*self.protocol)
425
476
  .headers(self.headers)
@@ -430,8 +481,7 @@ mod config {
430
481
  overrides: self.histogram_bucket_overrides,
431
482
  })
432
483
  .global_tags(self.global_tags)
433
- .build()
434
- .context("Failed to build otel exporter options")?;
484
+ .build();
435
485
 
436
486
  Ok(options)
437
487
  }