openvm_stark_sdk/
nvtx_tracing.rs1use std::sync::Arc;
2
3use tracing::{
4 field::{Field, Visit},
5 Id, Subscriber,
6};
7use tracing_subscriber::{registry::LookupSpan, Layer};
8
9pub struct NvtxLayer {
16 config: NvtxConfig,
17}
18
19pub struct NvtxConfig {
20 pub max_level: tracing::Level,
21}
22
23impl Default for NvtxConfig {
24 fn default() -> Self {
25 Self {
26 max_level: tracing::Level::INFO,
27 }
28 }
29}
30
31impl NvtxLayer {
32 pub fn new(config: NvtxConfig) -> Self {
33 Self { config }
34 }
35}
36
37struct NvtxSpanData {
40 label: Arc<str>,
41}
42
43struct FieldCollector {
45 parts: Vec<String>,
46}
47
48impl FieldCollector {
49 fn new() -> Self {
50 Self { parts: Vec::new() }
51 }
52
53 fn into_suffix(self) -> Option<String> {
54 if self.parts.is_empty() {
55 None
56 } else {
57 Some(self.parts.join(","))
58 }
59 }
60}
61
62impl Visit for FieldCollector {
63 fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) {
64 self.parts.push(format!("{}={:?}", field.name(), value));
65 }
66
67 fn record_i64(&mut self, field: &Field, value: i64) {
68 self.parts.push(format!("{}={}", field.name(), value));
69 }
70
71 fn record_u64(&mut self, field: &Field, value: u64) {
72 self.parts.push(format!("{}={}", field.name(), value));
73 }
74
75 fn record_str(&mut self, field: &Field, value: &str) {
76 self.parts.push(format!("{}={}", field.name(), value));
77 }
78}
79
80fn build_label(meta: &tracing::Metadata<'_>, attrs: &tracing::span::Attributes<'_>) -> Arc<str> {
81 let mut collector = FieldCollector::new();
82 attrs.record(&mut collector);
83
84 let name = meta.name();
85 match collector.into_suffix() {
86 Some(suffix) => Arc::from(format!("{name}{{{suffix}}}")),
87 None => Arc::from(name),
88 }
89}
90
91impl<S> Layer<S> for NvtxLayer
92where
93 S: Subscriber + for<'a> LookupSpan<'a>,
94{
95 fn on_new_span(
96 &self,
97 attrs: &tracing::span::Attributes<'_>,
98 id: &Id,
99 ctx: tracing_subscriber::layer::Context<'_, S>,
100 ) {
101 let Some(span) = ctx.span(id) else { return };
102 let meta = span.metadata();
103
104 if meta.level() > &self.config.max_level {
105 return;
106 }
107
108 let label = build_label(meta, attrs);
109 span.extensions_mut().insert(NvtxSpanData { label });
110 }
111
112 fn on_record(
113 &self,
114 id: &Id,
115 values: &tracing::span::Record<'_>,
116 ctx: tracing_subscriber::layer::Context<'_, S>,
117 ) {
118 let Some(span) = ctx.span(id) else { return };
119 let mut exts = span.extensions_mut();
120 let Some(data) = exts.get_mut::<NvtxSpanData>() else {
121 return;
122 };
123 let mut collector = FieldCollector::new();
127 values.record(&mut collector);
128 if let Some(suffix) = collector.into_suffix() {
129 let base = data.label.trim_end_matches('}');
130 let sep = if base.len() < data.label.len() {
131 ","
132 } else {
133 "{"
134 };
135 data.label = Arc::from(format!("{base}{sep}{suffix}}}"));
136 }
137 }
138
139 fn on_enter(&self, id: &Id, ctx: tracing_subscriber::layer::Context<'_, S>) {
140 let Some(span) = ctx.span(id) else { return };
141 let exts = span.extensions();
142 let Some(data) = exts.get::<NvtxSpanData>() else {
143 return;
144 };
145 nvtx::range_push!("{}", data.label);
146 }
147
148 fn on_exit(&self, id: &Id, ctx: tracing_subscriber::layer::Context<'_, S>) {
149 let Some(span) = ctx.span(id) else { return };
150 let exts = span.extensions();
151 if exts.get::<NvtxSpanData>().is_some() {
152 nvtx::range_pop!();
153 }
154 }
155}
156
157#[cfg(test)]
158mod tests {
159 use tracing::info_span;
160 use tracing_subscriber::{layer::SubscriberExt, Registry};
161
162 use super::*;
163
164 #[test]
165 fn test_nvtx_layer_with_fields() {
166 let subscriber = Registry::default().with(NvtxLayer::new(NvtxConfig::default()));
167 tracing::subscriber::with_default(subscriber, || {
168 let span = info_span!("prove_segment", segment = 42, phase = "prover");
170 let _guard = span.enter();
171 let inner = info_span!("stark_prove");
173 let _inner_guard = inner.enter();
174 });
175 }
176
177 #[test]
178 fn test_nvtx_layer_late_bound_fields() {
179 let subscriber = Registry::default().with(NvtxLayer::new(NvtxConfig::default()));
180 tracing::subscriber::with_default(subscriber, || {
181 let span = info_span!("step", idx = tracing::field::Empty);
183 let _guard = span.enter();
184 span.record("idx", 7);
185 let span2 = info_span!("work", phase = "init", result = tracing::field::Empty);
187 let _guard2 = span2.enter();
188 span2.record("result", 42);
189 });
190 }
191}