Skip to content

Commit 5abdf8c

Browse files
authored
feat: add independent OTLP span instrumentation for V8 event loop (#674)
1 parent 564363c commit 5abdf8c

File tree

6 files changed

+145
-5
lines changed

6 files changed

+145
-5
lines changed

Cargo.lock

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

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,9 +195,9 @@ yoke = { version = "0.7.4", features = ["derive"] }
195195

196196
opentelemetry = "0.27.0"
197197
opentelemetry-http = "0.27.0"
198-
opentelemetry-otlp = { version = "0.27.0", features = ["logs", "http-proto", "http-json"] }
198+
opentelemetry-otlp = { version = "0.27.0", features = ["logs", "trace", "http-proto", "http-json"] }
199199
opentelemetry-semantic-conventions = { version = "0.27.0", features = ["semconv_experimental"] }
200-
opentelemetry_sdk = "0.27.0"
200+
opentelemetry_sdk = { version = "0.27.0", features = ["rt-tokio"] }
201201

202202
# upstream resolvers
203203
deno_npm_cache = "0.2.0"

cli/src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ fn main() -> Result<ExitCode, anyhow::Error> {
9191
deno::versions::otel_runtime_config(),
9292
OtelConfig::default(),
9393
)?;
94+
base::runtime::otel::init_if_needed();
9495

9596
let ip = sub_matches.get_one::<String>("ip").cloned().unwrap();
9697
let ip = IpAddr::from_str(&ip)

crates/base/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ monch.workspace = true
5454
notify.workspace = true
5555
num-traits.workspace = true
5656
once_cell.workspace = true
57+
opentelemetry.workspace = true
58+
opentelemetry-otlp.workspace = true
59+
opentelemetry_sdk.workspace = true
5760
pin-project.workspace = true
5861
reqwest_v011.workspace = true
5962
rustls-pemfile.workspace = true

crates/base/src/runtime/mod.rs

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ use deno_facade::Metadata;
8080
use either::Either;
8181
use either::Either::Left;
8282
use either::Either::Right;
83+
use ext_event_worker::events::EventMetadata;
8384
use ext_event_worker::events::WorkerEventWithMetadata;
8485
use ext_runtime::external_memory::CustomAllocator;
8586
use ext_runtime::MemCheckWaker;
@@ -127,6 +128,7 @@ use crate::worker::Worker;
127128
mod ops;
128129
mod unsync;
129130

131+
pub mod otel;
130132
pub mod permissions;
131133

132134
const DEFAULT_ALLOC_CHECK_INT_MSEC: u64 = 1000;
@@ -1312,6 +1314,15 @@ where
13121314
};
13131315
}
13141316

1317+
let otel_attrs = {
1318+
let op_state = self.js_runtime.op_state();
1319+
let op_state_ref = op_state.borrow();
1320+
op_state_ref
1321+
.try_borrow::<EventMetadata>()
1322+
.map(otel::WorkerSpanAttrs::from_event_metadata)
1323+
.unwrap_or_default()
1324+
};
1325+
13151326
let inspector = self.inspector();
13161327
let mod_fut_ret = unsafe {
13171328
if let Err(err) = self.init_main_module().await {
@@ -1371,7 +1382,10 @@ where
13711382
op_state,
13721383
&maybe_cpu_usage_metrics_tx,
13731384
&mut accumulated_cpu_time_ns,
1374-
|| locker.js_runtime.mod_evaluate(main_module_id),
1385+
|| {
1386+
let _span = otel::start_span("v8.mod_evaluate", &otel_attrs);
1387+
locker.js_runtime.mod_evaluate(main_module_id)
1388+
},
13751389
))
13761390
}
13771391
.instrument(span),
@@ -1449,7 +1463,11 @@ where
14491463
locker.js_runtime.op_state(),
14501464
&maybe_cpu_usage_metrics_tx,
14511465
&mut accumulated_cpu_time_ns,
1452-
|| MaybeDenoRuntime::DenoRuntime(*locker).dispatch_load_event(),
1466+
|| {
1467+
let _span =
1468+
otel::start_span("v8.dispatch_load_event", &otel_attrs);
1469+
MaybeDenoRuntime::DenoRuntime(*locker).dispatch_load_event()
1470+
},
14531471
) {
14541472
return (Err(err), get_accumulated_cpu_time_ms!());
14551473
}
@@ -1482,7 +1500,10 @@ where
14821500
locker.js_runtime.op_state(),
14831501
&maybe_cpu_usage_metrics_tx,
14841502
&mut accumulated_cpu_time_ns,
1485-
|| MaybeDenoRuntime::DenoRuntime(&mut locker).dispatch_unload_event(),
1503+
|| {
1504+
let _span = otel::start_span("v8.dispatch_unload_event", &otel_attrs);
1505+
MaybeDenoRuntime::DenoRuntime(&mut locker).dispatch_unload_event()
1506+
},
14861507
) {
14871508
return (Err(err), get_accumulated_cpu_time_ms!());
14881509
}
@@ -1506,6 +1527,16 @@ where
15061527
let is_user_worker = self.conf.is_user_worker();
15071528
let global_waker = self.waker.clone();
15081529

1530+
// Collect worker identity for OTLP spans.
1531+
let otel_attrs = {
1532+
let op_state = self.js_runtime.op_state();
1533+
let op_state_ref = op_state.borrow();
1534+
op_state_ref
1535+
.try_borrow::<EventMetadata>()
1536+
.map(otel::WorkerSpanAttrs::from_event_metadata)
1537+
.unwrap_or_default()
1538+
};
1539+
15091540
let mut termination_request_fut = self
15101541
.termination_request_token
15111542
.clone()
@@ -1573,6 +1604,7 @@ where
15731604
Cow::Borrowed(waker)
15741605
};
15751606

1607+
let _poll_span = otel::start_span("v8.poll_event_loop", &otel_attrs);
15761608
js_runtime.poll_event_loop(
15771609
&mut std::task::Context::from_waker(waker.as_ref()),
15781610
PollEventLoopOptions {

crates/base/src/runtime/otel.rs

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
use std::sync::OnceLock;
2+
use std::time::SystemTime;
3+
4+
use ext_event_worker::events::EventMetadata;
5+
use opentelemetry::global;
6+
use opentelemetry::trace::Span as _;
7+
use opentelemetry::trace::SpanKind;
8+
use opentelemetry::trace::TraceContextExt;
9+
use opentelemetry::trace::Tracer;
10+
use opentelemetry::trace::TracerProvider as _;
11+
use opentelemetry::Context;
12+
use opentelemetry::KeyValue;
13+
use opentelemetry_otlp::WithExportConfig;
14+
use opentelemetry_sdk::trace::BatchSpanProcessor;
15+
use opentelemetry_sdk::trace::TracerProvider;
16+
17+
static PROVIDER: OnceLock<Option<TracerProvider>> = OnceLock::new();
18+
19+
pub fn init_if_needed() {
20+
PROVIDER.get_or_init(|| {
21+
if std::env::var("OTEL_EXPORTER_OTLP_ENDPOINT").is_err() {
22+
return None;
23+
}
24+
25+
let exporter = opentelemetry_otlp::SpanExporter::builder()
26+
.with_http()
27+
.with_export_config(opentelemetry_otlp::ExportConfig::default())
28+
.build()
29+
.expect("failed to build OTLP span exporter");
30+
31+
let processor =
32+
BatchSpanProcessor::builder(exporter, opentelemetry_sdk::runtime::Tokio)
33+
.build();
34+
35+
let provider = TracerProvider::builder()
36+
.with_span_processor(processor)
37+
.build();
38+
39+
global::set_tracer_provider(provider.clone());
40+
Some(provider)
41+
});
42+
}
43+
44+
/// Worker identity and custom otel attributes collected once at `run()` entry.
45+
#[derive(Clone, Default)]
46+
pub struct WorkerSpanAttrs(Vec<KeyValue>);
47+
48+
impl WorkerSpanAttrs {
49+
pub fn from_event_metadata(meta: &EventMetadata) -> Self {
50+
let mut attrs = Vec::new();
51+
52+
if let Some(sp) = &meta.service_path {
53+
attrs.push(KeyValue::new("worker.service_path", sp.clone()));
54+
}
55+
if let Some(id) = meta.execution_id {
56+
attrs.push(KeyValue::new("worker.key", id.to_string()));
57+
}
58+
if let Some(extra) = &meta.otel_attributes {
59+
for (k, v) in extra {
60+
attrs.push(KeyValue::new(k.clone(), v.clone()));
61+
}
62+
}
63+
64+
Self(attrs)
65+
}
66+
}
67+
68+
/// Start a named span with worker attributes. Returns `None` if OTLP is not
69+
/// configured.
70+
pub fn start_span(
71+
name: &'static str,
72+
attrs: &WorkerSpanAttrs,
73+
) -> Option<OtelSpanGuard> {
74+
let provider = PROVIDER.get()?.as_ref()?;
75+
let tracer = provider.tracer("edge-runtime");
76+
let mut span = tracer
77+
.span_builder(name)
78+
.with_kind(SpanKind::Internal)
79+
.with_start_time(SystemTime::now())
80+
.start(&tracer);
81+
82+
for kv in &attrs.0 {
83+
span.set_attribute(kv.clone());
84+
}
85+
86+
let cx = Context::current_with_span(span);
87+
Some(OtelSpanGuard { _cx: cx })
88+
}
89+
90+
/// Ends the span on drop.
91+
pub struct OtelSpanGuard {
92+
_cx: Context,
93+
}
94+
95+
impl Drop for OtelSpanGuard {
96+
fn drop(&mut self) {
97+
self._cx.span().end();
98+
}
99+
}

0 commit comments

Comments
 (0)