1#![doc = include_str!("../FEATURES.mkd")]
49#![cfg_attr(tracing_tracy_docs, feature(doc_auto_cfg))]
50
51use client::{Client, Span};
52pub use config::{Config, DefaultConfig};
53use std::sync::atomic::{AtomicUsize, Ordering};
54use std::{fmt::Write, mem};
55use tracing_core::{
56 field::{Field, Visit},
57 span::{Attributes, Id, Record},
58 Event, Subscriber,
59};
60use tracing_subscriber::fmt::format::FormatFields;
61use tracing_subscriber::{
62 layer::{Context, Layer},
63 registry,
64};
65use utils::{StrCache, StrCacheGuard, VecCell};
66
67pub use client;
68mod config;
69
70type TracyFields<C> = tracing_subscriber::fmt::FormattedFields<<C as Config>::Formatter>;
71
72thread_local! {
73 static TRACY_SPAN_STACK: VecCell<(Span, u64)> = const { VecCell::new() };
75}
76
77#[derive(Clone)]
88pub struct TracyLayer<C = DefaultConfig> {
89 config: C,
90 client: Client,
91}
92
93impl<C> TracyLayer<C> {
94 #[must_use]
98 pub fn new(config: C) -> Self {
99 Self {
100 config,
101 client: Client::start(),
102 }
103 }
104}
105
106impl<C: Config> TracyLayer<C> {
107 fn truncate_span_to_length<'a>(
108 &self,
109 data: &'a str,
110 file: &str,
111 function: &str,
112 error_msg: &'static str,
113 ) -> &'a str {
114 self.truncate_to_length(
115 usize::from(u16::MAX) - 2 - 4 - 4 - function.len() - 1 - file.len() - 1,
117 data,
118 error_msg,
119 )
120 }
121
122 fn truncate_to_length<'a>(
123 &self,
124 mut max_len: usize,
125 data: &'a str,
126 error_msg: &'static str,
127 ) -> &'a str {
128 if data.len() >= max_len {
129 while !data.is_char_boundary(max_len) {
130 max_len -= 1;
131 }
132 self.config.on_error(&self.client, error_msg);
133 &data[..max_len]
134 } else {
135 data
136 }
137 }
138}
139
140impl Default for TracyLayer {
141 fn default() -> Self {
142 Self::new(DefaultConfig::default())
143 }
144}
145
146static MAX_CACHE_SIZE: AtomicUsize = AtomicUsize::new(8192);
147
148pub fn set_max_cache_size(max_bytes_used_per_thread: usize) {
159 MAX_CACHE_SIZE.store(max_bytes_used_per_thread, Ordering::Relaxed);
160}
161
162thread_local! {
163 static CACHE: StrCache = const { StrCache::new() };
164}
165
166impl<S, C> Layer<S> for TracyLayer<C>
167where
168 S: Subscriber + for<'a> registry::LookupSpan<'a>,
169 C: Config + 'static,
170{
171 fn on_new_span(&self, attrs: &Attributes<'_>, id: &Id, ctx: Context<'_, S>) {
172 let Some(span) = ctx.span(id) else { return };
173
174 let mut extensions = span.extensions_mut();
175 if extensions.get_mut::<TracyFields<C>>().is_none() {
176 let mut fields =
177 TracyFields::<C>::new(CACHE.with(|cache| cache.acquire().into_inner()));
178 if self
179 .config
180 .formatter()
181 .format_fields(fields.as_writer(), attrs)
182 .is_ok()
183 {
184 extensions.insert(fields);
185 }
186 }
187 }
188
189 fn on_record(&self, id: &Id, values: &Record<'_>, ctx: Context<'_, S>) {
190 let Some(span) = ctx.span(id) else { return };
191
192 let mut extensions = span.extensions_mut();
193 if let Some(fields) = extensions.get_mut::<TracyFields<C>>() {
194 let _ = self.config.formatter().add_fields(fields, values);
195 } else {
196 let mut fields =
197 TracyFields::<C>::new(CACHE.with(|cache| cache.acquire().into_inner()));
198 if self
199 .config
200 .formatter()
201 .format_fields(fields.as_writer(), values)
202 .is_ok()
203 {
204 extensions.insert(fields);
205 }
206 }
207 }
208
209 fn on_event(&self, event: &Event, _: Context<'_, S>) {
210 CACHE.with(|cache| {
211 let mut buf = cache.acquire();
212 let mut visitor = TracyEventFieldVisitor {
213 dest: &mut buf,
214 first: true,
215 frame_mark: false,
216 };
217
218 event.record(&mut visitor);
219 if !visitor.first {
220 self.client.message(
221 self.truncate_to_length(
222 (u16::MAX - 1).into(),
223 visitor.dest,
224 "event message is too long and was truncated",
225 ),
226 self.config.stack_depth(event.metadata()),
227 );
228 }
229 if visitor.frame_mark {
230 self.client.frame_mark();
231 }
232 });
233 }
234
235 fn on_enter(&self, id: &Id, ctx: Context<S>) {
236 let Some(span) = ctx.span(id) else { return };
237
238 let extensions = span.extensions();
239 let fields = extensions.get::<TracyFields<C>>();
240 let stack_frame = {
241 let metadata = span.metadata();
242 let file = metadata.file().unwrap_or("<not available>");
243 let line = metadata.line().unwrap_or(0);
244 let span = |name: &str| {
245 (
246 self.client.clone().span_alloc(
247 Some(self.truncate_span_to_length(
248 name,
249 file,
250 "",
251 "span information is too long and was truncated",
252 )),
253 "",
254 file,
255 line,
256 self.config.stack_depth(metadata),
257 ),
258 id.into_u64(),
259 )
260 };
261
262 match fields {
263 None => span(metadata.name()),
264 Some(fields) if fields.is_empty() => span(metadata.name()),
265 Some(fields) if self.config.format_fields_in_zone_name() => CACHE.with(|cache| {
266 let mut buf = cache.acquire();
267 let _ = write!(buf, "{}{{{}}}", metadata.name(), fields.fields);
268 span(&buf)
269 }),
270 Some(fields) => {
271 let span = span(metadata.name());
272 span.0.emit_text(self.truncate_to_length(
273 (u16::MAX - 1).into(),
274 &fields.fields,
275 "span field values are too long and were truncated",
276 ));
277 span
278 }
279 }
280 };
281
282 TRACY_SPAN_STACK.with(|s| {
283 s.push(stack_frame);
284 });
285 }
286
287 fn on_exit(&self, id: &Id, _: Context<S>) {
288 let stack_frame = TRACY_SPAN_STACK.with(VecCell::pop);
289
290 if let Some((span, span_id)) = stack_frame {
291 if id.into_u64() != span_id {
292 self.config.on_error(
293 &self.client,
294 "Tracing spans exited out of order! \
295 Trace might not be accurate for this span stack.",
296 );
297 }
298 drop(span);
299 } else {
300 self.config.on_error(
301 &self.client,
302 "Exiting a tracing span, but got nothing on the tracy span stack!",
303 );
304 }
305 }
306
307 fn on_close(&self, id: Id, ctx: Context<'_, S>) {
308 let Some(span) = ctx.span(&id) else { return };
309
310 if let Some(fields) = span.extensions_mut().get_mut::<TracyFields<C>>() {
311 let buf = mem::take(&mut fields.fields);
312 CACHE.with(|cache| drop(StrCacheGuard::new(cache, buf)));
313 };
314 }
315}
316
317struct TracyEventFieldVisitor<'a> {
318 dest: &'a mut String,
319 frame_mark: bool,
320 first: bool,
321}
322
323impl Visit for TracyEventFieldVisitor<'_> {
324 fn record_bool(&mut self, field: &Field, value: bool) {
325 match (value, field.name()) {
326 (_, "tracy.frame_mark") => self.frame_mark = value,
327 (true, _) => self.record_str(field, "true"),
328 (false, _) => self.record_str(field, "false"),
329 }
330 }
331
332 fn record_str(&mut self, field: &Field, value: &str) {
333 let name = field.name();
334 let alloc_always_size = name.len() + " = ".len() + value.len();
335 if self.first {
336 self.dest.reserve(alloc_always_size);
337 self.first = false;
338 } else {
339 self.dest.reserve(", ".len() + alloc_always_size);
340 self.dest.push_str(", ");
341 }
342
343 self.dest.push_str(name);
344 self.dest.push_str(" = ");
345 self.dest.push_str(value);
346 }
347
348 fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) {
349 if self.first {
352 self.first = false;
353 let _ = write!(self.dest, "{} = {value:?}", field.name());
354 } else {
355 let _ = write!(self.dest, ", {} = {value:?}", field.name());
356 }
357 }
358}
359
360#[cfg(test)]
361mod tests;
362#[cfg(test)]
363fn main() {
364 if std::env::args_os().any(|p| p == std::ffi::OsStr::new("--bench")) {
365 tests::bench();
366 } else {
367 tests::test();
368 }
369}
370
371mod utils {
372 use crate::MAX_CACHE_SIZE;
373 use std::cell::{Cell, UnsafeCell};
374 use std::mem;
375 use std::mem::ManuallyDrop;
376 use std::ops::{Deref, DerefMut};
377 use std::sync::atomic::Ordering;
378
379 pub struct VecCell<T>(UnsafeCell<Vec<T>>);
380
381 impl<T> VecCell<T> {
382 pub const fn new() -> Self {
383 Self(UnsafeCell::new(Vec::new()))
384 }
385
386 pub fn push(&self, item: T) {
387 unsafe { &mut *self.0.get() }.push(item);
391 }
392
393 pub fn pop(&self) -> Option<T> {
394 unsafe { &mut *self.0.get() }.pop()
398 }
399 }
400
401 pub struct StrCache {
402 str_bufs: VecCell<String>,
403 total_size: Cell<usize>,
404 }
405
406 impl StrCache {
407 pub const fn new() -> Self {
408 Self {
409 str_bufs: VecCell::new(),
410 total_size: Cell::new(0),
411 }
412 }
413
414 pub fn acquire(&self) -> StrCacheGuard {
415 StrCacheGuard::new(
416 self,
417 self.str_bufs
418 .pop()
419 .map(|buf| {
421 self.total_size.set(self.total_size.get() - buf.capacity());
422 buf
423 })
424 .unwrap_or_else(|| String::with_capacity(64)),
425 )
426 }
427
428 fn release(&self, mut buf: String) {
429 let new_cache_size = self.total_size.get().saturating_add(buf.capacity());
430 if new_cache_size == usize::MAX {
431 return;
434 };
435 let max_size = MAX_CACHE_SIZE.load(Ordering::Relaxed);
436 if buf.capacity() == 0 || max_size == 0 {
437 return;
438 }
439
440 buf.clear();
441 self.str_bufs.push(buf);
442 self.total_size.set(new_cache_size);
443
444 if new_cache_size > max_size {
445 unsafe { &mut *self.str_bufs.0.get() }.sort_unstable_by_key(String::capacity);
449 if let Some(trimmed) = self.str_bufs.pop() {
450 self.total_size
451 .set(self.total_size.get() - trimmed.capacity());
452 }
453 }
454 }
455 }
456
457 pub struct StrCacheGuard<'a> {
458 cache: &'a StrCache,
459 buf: String,
460 }
461
462 impl<'a> StrCacheGuard<'a> {
463 pub fn new(cache: &'a StrCache, buf: String) -> Self {
464 Self { cache, buf }
465 }
466
467 pub fn into_inner(self) -> String {
468 let mut this = ManuallyDrop::new(self);
469 mem::take(&mut this.buf)
470 }
471 }
472
473 impl Deref for StrCacheGuard<'_> {
474 type Target = String;
475
476 fn deref(&self) -> &Self::Target {
477 &self.buf
478 }
479 }
480
481 impl DerefMut for StrCacheGuard<'_> {
482 fn deref_mut(&mut self) -> &mut Self::Target {
483 &mut self.buf
484 }
485 }
486
487 impl Drop for StrCacheGuard<'_> {
488 fn drop(&mut self) {
489 self.cache.release(mem::take(&mut self.buf));
490 }
491 }
492}