tracy_client/
lib.rs

1#![deny(unsafe_op_in_unsafe_fn, missing_docs)]
2#![cfg_attr(
3    not(feature = "enable"),
4    allow(unused_variables, unused_imports, unused_mut, dead_code)
5)]
6// TODO https://github.com/rust-lang/rust-clippy/issues/12017
7#![allow(clippy::let_unit_value)]
8//! This crate is a set of safe bindings to the client library of the [Tracy profiler].
9//!
10//! If you have already instrumented your application with `tracing`, consider the `tracing-tracy`
11//! crate.
12//!
13//! [Tracy profiler]: https://github.com/wolfpld/tracy
14//!
15//! # Important note
16//!
17//! Depending on the configuration Tracy may broadcast discovery packets to the local network and
18//! expose the data it collects in the background to that same network. Traces collected by Tracy
19//! may include source and assembly code as well.
20//!
21//! As thus, you may want make sure to only enable the `tracy-client` crate conditionally, via
22//! the `enable` feature flag provided by this crate.
23//!
24//! # Features
25//!
26//! The following crate features are provided to customize the functionality of the Tracy client:
27//!
28#![doc = include_str!("../FEATURES.mkd")]
29#![cfg_attr(tracy_client_docs, feature(doc_auto_cfg))]
30
31pub use crate::frame::{frame_image, frame_mark, Frame, FrameName};
32pub use crate::gpu::{
33    GpuContext, GpuContextCreationError, GpuContextType, GpuSpan, GpuSpanCreationError,
34};
35pub use crate::plot::{PlotConfiguration, PlotFormat, PlotLineStyle, PlotName};
36pub use crate::span::{Span, SpanLocation};
37use std::alloc;
38use std::ffi::CString;
39pub use sys;
40
41mod frame;
42mod gpu;
43mod plot;
44mod span;
45mod state;
46
47#[cfg(feature = "demangle")]
48pub mod demangle;
49
50/// /!\ /!\ Please don't rely on anything in this module T_T /!\ /!\
51#[doc(hidden)]
52pub mod internal {
53    pub use crate::{span::SpanLocation, sys};
54    pub use once_cell::sync::Lazy;
55    pub use std::any::type_name;
56    use std::ffi::CString;
57    pub use std::ptr::null;
58
59    #[cfg(feature = "demangle")]
60    pub mod demangle {
61        pub use crate::demangle::{default, internal::implementation};
62    }
63
64    #[inline(always)]
65    #[must_use]
66    pub fn make_span_location(
67        type_name: &'static str,
68        span_name: *const u8,
69        file: *const u8,
70        line: u32,
71    ) -> SpanLocation {
72        #[cfg(feature = "enable")]
73        {
74            let function_name = CString::new(&type_name[..type_name.len() - 3]).unwrap();
75            SpanLocation {
76                data: sys::___tracy_source_location_data {
77                    name: span_name.cast(),
78                    function: function_name.as_ptr(),
79                    file: file.cast(),
80                    line,
81                    color: 0,
82                },
83                _function_name: function_name,
84            }
85        }
86        #[cfg(not(feature = "enable"))]
87        crate::SpanLocation { _internal: () }
88    }
89
90    #[inline(always)]
91    #[must_use]
92    pub const unsafe fn create_frame_name(name: &'static str) -> crate::frame::FrameName {
93        crate::frame::FrameName(name)
94    }
95
96    #[inline(always)]
97    #[must_use]
98    pub const unsafe fn create_plot(name: &'static str) -> crate::plot::PlotName {
99        crate::plot::PlotName(name)
100    }
101
102    /// Safety: `name` must be null-terminated, and a `Client` must be enabled
103    #[inline(always)]
104    pub unsafe fn set_thread_name(name: *const u8) {
105        #[cfg(feature = "enable")]
106        unsafe {
107            let () = sys::___tracy_set_thread_name(name.cast());
108        }
109    }
110}
111
112/// A type representing an enabled Tracy client.
113///
114/// Obtaining a `Client` is required in order to instrument the application.
115///
116/// Multiple copies of a Client may be live at once. As long as at least one `Client` value lives,
117/// the `Tracy` client is enabled globally. In addition to collecting information through the
118/// instrumentation inserted by you, the Tracy client may automatically collect information about
119/// execution of the program while it is enabled. All this information may be stored in memory
120/// until a profiler application connects to the client to read the data.
121///
122/// Depending on the build configuration, the client may collect and make available machine
123/// and source code of the application as well as other potentially sensitive information.
124///
125/// When all of the `Client` values are dropped, the underlying Tracy client will be shut down as
126/// well. Shutting down the `Client` will discard any information gathered up to that point that
127/// still hasn't been delivered to the profiler application.
128pub struct Client(());
129
130/// Instrumentation methods for outputting events occurring at a specific instant.
131///
132/// Data provided by this instrumentation can largely be considered to be equivalent to logs.
133impl Client {
134    /// Output a message.
135    ///
136    /// Specifying a non-zero `callstack_depth` will enable collection of callstack for this
137    /// message. The number provided will limit the number of call frames collected. Note that
138    /// enabling callstack collection introduces a non-trivial amount of overhead to this call.
139    pub fn message(&self, message: &str, callstack_depth: u16) {
140        #[cfg(feature = "enable")]
141        unsafe {
142            let stack_depth = adjust_stack_depth(callstack_depth).into();
143            let () =
144                sys::___tracy_emit_message(message.as_ptr().cast(), message.len(), stack_depth);
145        }
146    }
147
148    /// Output a message with an associated color.
149    ///
150    /// Specifying a non-zero `callstack_depth` will enable collection of callstack for this
151    /// message. The number provided will limit the number of call frames collected. Note that
152    /// enabling callstack collection introduces a non-trivial amount of overhead to this call.
153    ///
154    /// The colour shall be provided as RGBA, where the least significant 8 bits represent the alpha
155    /// component and most significant 8 bits represent the red component.
156    pub fn color_message(&self, message: &str, rgba: u32, callstack_depth: u16) {
157        #[cfg(feature = "enable")]
158        unsafe {
159            let depth = adjust_stack_depth(callstack_depth).into();
160            let () = sys::___tracy_emit_messageC(
161                message.as_ptr().cast(),
162                message.len(),
163                rgba >> 8,
164                depth,
165            );
166        }
167    }
168}
169
170impl Client {
171    /// Set the current thread name to the provided value.
172    ///
173    /// # Panics
174    ///
175    /// This function will panic if the name contains interior null characters.
176    pub fn set_thread_name(&self, name: &str) {
177        #[cfg(feature = "enable")]
178        unsafe {
179            let name = CString::new(name).unwrap();
180            // SAFE: `name` is a valid null-terminated string.
181            internal::set_thread_name(name.as_ptr().cast());
182        }
183    }
184}
185
186/// Convenience macro for [`Client::set_thread_name`] on the current client.
187///
188/// Note that any interior null characters terminate the name early. This is not checked for.
189///
190/// # Panics
191///
192/// - If a `Client` isn't currently running.
193#[macro_export]
194macro_rules! set_thread_name {
195    ($name: literal) => {{
196        $crate::Client::running().expect("set_thread_name! without a running Client");
197        unsafe {
198            // SAFE: `name` is a valid null-terminated string.
199            $crate::internal::set_thread_name(concat!($name, "\0").as_ptr().cast())
200        }
201    }};
202}
203
204/// A profiling wrapper around another allocator.
205///
206/// See documentation for [`std::alloc`] for more information about global allocators.
207///
208/// Note that this wrapper will start up the client on the first allocation, if not enabled
209/// already.
210///
211/// # Examples
212///
213/// In your executable, add:
214///
215/// ```rust
216/// # use tracy_client::*;
217/// #[global_allocator]
218/// static GLOBAL: ProfiledAllocator<std::alloc::System> =
219///     ProfiledAllocator::new(std::alloc::System, 100);
220/// ```
221pub struct ProfiledAllocator<T>(T, u16);
222
223impl<T> ProfiledAllocator<T> {
224    /// Construct a new `ProfiledAllocator`.
225    ///
226    /// Specifying a non-zero `callstack_depth` will enable collection of callstack for this
227    /// message. The number provided will limit the number of call frames collected. Note that
228    /// enabling callstack collection introduces a non-trivial amount of overhead to each
229    /// allocation and deallocation.
230    pub const fn new(inner_allocator: T, callstack_depth: u16) -> Self {
231        Self(inner_allocator, adjust_stack_depth(callstack_depth))
232    }
233
234    fn emit_alloc(&self, ptr: *mut u8, size: usize) {
235        #[cfg(feature = "enable")]
236        unsafe {
237            Client::start();
238            if self.1 == 0 {
239                let () = sys::___tracy_emit_memory_alloc(ptr.cast(), size, 1);
240            } else {
241                let () =
242                    sys::___tracy_emit_memory_alloc_callstack(ptr.cast(), size, self.1.into(), 1);
243            }
244        }
245    }
246
247    fn emit_free(&self, ptr: *mut u8) {
248        #[cfg(feature = "enable")]
249        unsafe {
250            if self.1 == 0 {
251                let () = sys::___tracy_emit_memory_free(ptr.cast(), 1);
252            } else {
253                let () = sys::___tracy_emit_memory_free_callstack(ptr.cast(), self.1.into(), 1);
254            }
255        }
256    }
257}
258
259unsafe impl<T: alloc::GlobalAlloc> alloc::GlobalAlloc for ProfiledAllocator<T> {
260    unsafe fn alloc(&self, layout: alloc::Layout) -> *mut u8 {
261        let alloc = unsafe {
262            // SAFE: all invariants satisfied by the caller.
263            self.0.alloc(layout)
264        };
265        self.emit_alloc(alloc, layout.size());
266        alloc
267    }
268
269    unsafe fn dealloc(&self, ptr: *mut u8, layout: alloc::Layout) {
270        self.emit_free(ptr);
271        unsafe {
272            // SAFE: all invariants satisfied by the caller.
273            self.0.dealloc(ptr, layout);
274        }
275    }
276
277    unsafe fn alloc_zeroed(&self, layout: alloc::Layout) -> *mut u8 {
278        let alloc = unsafe {
279            // SAFE: all invariants satisfied by the caller.
280            self.0.alloc_zeroed(layout)
281        };
282        self.emit_alloc(alloc, layout.size());
283        alloc
284    }
285
286    unsafe fn realloc(&self, ptr: *mut u8, layout: alloc::Layout, new_size: usize) -> *mut u8 {
287        self.emit_free(ptr);
288        let alloc = unsafe {
289            // SAFE: all invariants satisfied by the caller.
290            self.0.realloc(ptr, layout, new_size)
291        };
292        self.emit_alloc(alloc, new_size);
293        alloc
294    }
295}
296
297/// Clamp the stack depth to the maximum supported by Tracy.
298pub(crate) const fn adjust_stack_depth(depth: u16) -> u16 {
299    #[cfg(windows)]
300    {
301        62 ^ ((depth ^ 62) & 0u16.wrapping_sub((depth < 62) as _))
302    }
303    #[cfg(not(windows))]
304    {
305        depth
306    }
307}