Skip to main content

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