tracy_gizmos/lib.rs
1#![cfg_attr(docsrs, feature(doc_cfg), deny(rustdoc::broken_intra_doc_links))]
2#![cfg_attr(any(doc, feature = "enabled"), deny(missing_docs))]
3#![cfg_attr(not(feature = "enabled"), allow(unused_variables))]
4#![cfg_attr(
5 feature = "unstable-function-names",
6 allow(incomplete_features),
7 feature(const_type_name),
8 feature(generic_const_exprs),
9)]
10
11//! Bindings for the client library of the
12//! [Tracy](https://github.com/wolfpld/tracy) profiler.
13//!
14//! Refer to the Tracy manual for details and profiler server usage.
15//!
16//! # How to use
17//!
18//! Add `tracy-gizmos` to your `Cargo.toml`:
19//!
20//! ```toml
21//! [dependencies]
22//! tracy-gizmos = { version = "0.0.1", features = ["enabled"] }
23//! ```
24//!
25//! Note that instrumentation is *disabled* by default.
26//!
27//! The usage is pretty straight-forward (for more details read the docs):
28//!
29//! ```no_run
30//! # fn work() { todo!() }
31//! fn main() {
32//! let tracy = tracy_gizmos::start_capture();
33//! tracy_gizmos::zone!("main");
34//! work();
35//! }
36//! ```
37//!
38//! The [`#[instrument]`][instrument] attribute provies an easy way to
39//! add Tracy zones to functions. A function annotated with
40//! `#[instrument]` will create and enter a zone with that function's
41//! name everytime the function is called.
42//!
43//! For example:
44//!
45//! ```ignore
46//! # // this doctest is ignored, because it is impossible
47//! # // to run it with cfg(feature = "attributes").
48//! #[tracy_gizmos::instrument]
49//! fn work() {
50//! // do stuff
51//! }
52//! ```
53//!
54//! You can find more examples showing how to use this crate
55//! [here][examples].
56//!
57//! [examples]: https://github.com/den-mentiei/tracy-gizmos/tree/main/examples
58//!
59//! # Features
60//!
61//! - **`enabled`** - enables the instrumentation and everything
62//! related to it.
63//! - **`attributes`** - includes support for the `#[instrument]` attribute.
64//! - **`unstable-function-names`** *(nightly only)* -
65//! includes the enclosing function name into every zone without
66//! additional runtime overhead.
67//!
68//! # Tracy features
69//!
70//! Tracy client functionality can be controlled pretty granularly.
71//! Refer to the Tracy's manual for more details. A corresponding
72//! define is listed for each feature, which can be used to search the
73//! Tracy's documentation or source code, if needed.
74//!
75//! The following features are available:
76//!
77//! - **`crash-handler`** - enables Tracy's crash handler, which
78//! intercepts application crashes and ensures the remaining profiling
79//! data is sent to the server together with a crash report details.
80//! Influences `TRACY_NO_CRASH_HANDLER`.
81//! - **`system-tracing`** - enables system-level tracing information
82//! collection (assuming that the profiled program was granted the
83//! priveleges needed, e.g. run as root or Administrator). Influences
84//! `TRACY_NO_SYSTEM_TRACING`.
85//! - **`context-switch`** - enables context switch information
86//! collection (assuming having the privelege, as above), which allows
87//! to see when a zone was actually executed or was waiting to be
88//! resumed. Influences `TRACY_NO_CONTEXT_SWITCH`.
89//! - **`sampling`** - enables the callstack sampling to augment
90//! instrumented data (requires privelege escalation on Windows).
91//! Influences `TRACY_NO_SAMPLING`.
92//! - **`callstack-inlines`** - enables the inline frames retrieval in
93//! callstacks, which provides more precise information but is
94//! magnitude slower. Influences `TRACY_NO_CALLSTACK_INLINES`.
95//! - **`hw-counters`** - enables the hardware performance counters
96//! sampling (available only on Linux or WSL): IPC, branch
97//! mispredicts, cache misses. Influences
98//! `TRACY_NO_SAMPLE_RETIREMENT`, `TRACY_NO_SAMPLE_BRANCH` and
99//! `TRACY_NO_SAMPLE_CACHE`.
100//! - **`code-transfer`** - enables the executable code retrieval,
101//! which captures parts of the application code for further analysis
102//! in Tracy. Be *extra careful* when working with non-public code!
103//! Influences `TRACY_NO_CODE_TRANSFER`.
104//! - **`vsync`** - enables the hardware Vsync events capture
105//! (assuming having the privilege), which will be reported as frame
106//! events per monitor. Influences `TRACY_NO_VSYNC_CAPTURE`.
107//! - **`no-exit`** - enables the short-lived application profiling
108//! improvement. When `TRACY_NO_EXIT` environment variable is set to
109//! `1`, profiled application will wait for the server connection to
110//! transfer the data, even if it has already finished executing.
111//! Influences `TRACY_NO_EXIT`.
112//! - **`broadcast`** - enables the local network announcement, so
113//! profiling servers can find the client. Influences
114//! `TRACY_NO_BROADCAST`.
115//! - **`only-localhost`** *(enabled by default)* - restricts Tracy to
116//! only listening on the localhost network interface. Influences
117//! `TRACY_ONLY_LOCALHOST`.
118//! - **`only-ipv4`** - restricts Tracy to only listenting on IPv4
119//! network interfaces. Influences `TRACY_ONLY_IPV4`.
120
121#[cfg(feature = "enabled")]
122use std::sync::atomic::{AtomicBool, Ordering};
123use std::marker::PhantomData;
124
125#[cfg_attr(docsrs, doc(cfg(feature = "attributes")))]
126#[doc(inline)]
127#[cfg(feature = "attributes")]
128pub use attrs::{instrument, capture};
129
130mod color;
131mod memory;
132mod plot;
133
134pub use color::*;
135pub use plot::*;
136
137/// Sets the current thread's name.
138///
139/// It is recommended to *always* use it in every thread, which uses
140/// Tracy capabilities.
141///
142/// If not set, Tracy will try to capture thread names through
143/// operating system data if context switch capture is active.
144/// However, this is only a fallback mechanism, and it shouldn't be
145/// relied upon.
146///
147/// # Examples
148///
149/// Ypu can specify a compile-time constant name for a particular
150/// thread, for example, a dedicated I/O thread:
151///
152/// ```no_run
153/// # use tracy_gizmos::*;
154/// # fn loop_and_do_io() {}
155/// std::thread::spawn(|| {
156/// set_thread_name!("I/O processor");
157/// zone!("I/O requests"); // Tracy will show it on this thread track.
158/// loop_and_do_io();
159/// });
160/// ```
161///
162/// You can make a name in runtime, for example, when you have an
163/// arbitrary amount of worker threads:
164///
165/// ```no_run
166/// # use tracy_gizmos::*;
167/// # fn get_next_worker_id() -> u32 { 0 }
168/// # fn loop_and_do_work() {}
169/// let id = get_next_worker_id();
170/// std::thread::spawn(move || {
171/// set_thread_name!("worker-thread {}", id);
172/// zone!("Working"); // Tracy will show it on this thread track.
173/// loop_and_do_work();
174/// });
175/// ```
176#[macro_export]
177#[cfg(any(doc, feature = "enabled"))]
178macro_rules! set_thread_name {
179 ($name:literal) => {
180 // SAFETY: We null-terminate the string.
181 unsafe {
182 $crate::details::set_thread_name(concat!($name, '\0').as_ptr());
183 }
184 };
185
186 ($format:literal, $($args:expr),*) => {{
187 let name = format!(concat!($format, '\0'), $($args),*).into_bytes();
188 // SAFETY: We null-terminated the string during formatting.
189 unsafe {
190 let name = std::ffi::CString::from_vec_with_nul_unchecked(name);
191 $crate::details::set_thread_name(name.as_ptr().cast());
192 }
193 }};
194}
195
196#[macro_export]
197#[cfg(all(not(doc), not(feature = "enabled")))]
198macro_rules! set_thread_name {
199 ($name:literal) => {
200 // Silence unused expression warning.
201 _ = $name;
202 };
203
204 ($format:literal, $($args:expr),*) => {{
205 // Silence unused expression warnings.
206 _ = $format;
207 $(
208 _ = $args;
209 )*
210 }};
211}
212
213/// Sends a message to Tracy's log.
214///
215/// Fast navigation in large data sets and correlating zones with what
216/// was happening in the application may be difficult. To ease these
217/// issues, Tracy provides a message log functionality. You can send
218/// messages (e.g. debug log output) using this macro.
219///
220/// # Examples
221///
222/// Just sending a message is straightforward:
223///
224/// ```no_run
225/// # use tracy_gizmos::*;
226/// message!("App started.");
227/// ```
228///
229/// Optionally, a custom [`Color`] could be assigned to the message.
230///
231/// ```no_run
232/// # use tracy_gizmos::*;
233/// message!(Color::YELLOW, "App failed to find something.");
234/// ```
235///
236/// ## Dynamic messages
237///
238/// It is also possible to use dynamic data as the message text.
239///
240/// The profiler will allocate a temporary internal buffer and copy
241/// the passed value there. Hence, this operation involves a small
242/// run-time cost.
243///
244/// Be aware that the passed text couldn't be larger than 64
245/// Kb.
246///
247/// ```no_run
248/// # use tracy_gizmos::*;
249/// # let i = 0;
250/// # let file_path = "file".to_string();
251/// message!("Trying {}", i);
252/// message!(&file_path);
253/// message!(Color::GREEN, "{} is good!", file_path);
254/// ```
255#[macro_export]
256#[cfg(any(doc, feature = "enabled"))]
257macro_rules! message {
258 ($text:literal) => {
259 // SAFETY: We null-terminate the string.
260 unsafe {
261 $crate::details::message(concat!($text, '\0').as_ptr());
262 }
263 };
264
265 ($text:expr) => {
266 $crate::details::message_size($text);
267 };
268
269 ($format:literal, $($args:expr),*) => {
270 let _text = format!($format, $($args),*);
271 $crate::details::message_size(&_text);
272 };
273
274 ($color:expr, $text:literal) => {
275 // SAFETY: We null-terminate the string.
276 unsafe {
277 $crate::details::message_color(
278 concat!($text, '\0').as_ptr(),
279 $color,
280 );
281 }
282 };
283
284 ($color:expr, $text:expr) => {
285 $crate::details::message_size_color(
286 $text,
287 $color,
288 );
289 };
290
291 ($color:expr, $format:literal, $($args:expr),*) => {
292 let _text = format!($format, $($args),*);
293 $crate::details::message_size_color(&_text, $color);
294 };
295}
296
297#[macro_export]
298#[cfg(all(not(doc), not(feature = "enabled")))]
299macro_rules! message {
300 ($text:literal) => {};
301
302 ($whatever:expr $(, $text:literal)?) => {
303 // Silences unused expression warning.
304 _ = $whatever;
305 };
306
307 ($format:literal, $($args:expr),*) => {
308 // Silence unused expression warnings.
309 $(
310 _ = $args;
311 )*
312 };
313
314 ($color:expr, $text:expr) => {
315 // Silence unused expression warnings.
316 _ = $color;
317 _ = $text;
318 };
319
320 ($color:expr, $format:literal, $($args:expr),*) => {
321 // Silence unused expression warnings.
322 _ = $color;
323 $(
324 _ = $args;
325 )*
326 };
327}
328
329/// Marks the completed frame end moment.
330///
331/// Program's execution could happen in frame-sized chunks, e.g. a
332/// rendering or I/O driven loop.
333///
334/// Note, that this is fully optional, as lots of applications do not
335/// even us the concept of a frame.
336///
337/// Each frame starts *immediately* after previous one has ended.
338///
339/// Some types of frames are discontinuous by their natures - they are
340/// executed periodically, with a pause between each run. E.g. a
341/// physics processing step in a game loop or an audio or save
342/// callback on a separate thread. Tracy can also track this kind of
343/// frames.
344///
345/// Frame types *must not* be mixed. For each frame set, identified by
346/// an unique name, use either a continuous or discontinuous frame
347/// only!
348///
349/// Under the hood it declares a local [`Frame`].
350///
351/// # Examples
352///
353/// Here comes all possible and interesting cases of frame marking.
354///
355/// ## Main frame
356///
357/// As simple as:
358///
359/// ```no_run
360/// # use tracy_gizmos::*;
361/// # fn update() { todo!() }
362/// # fn render() { todo!() }
363/// loop {
364/// update();
365/// render();
366/// frame!();
367/// }
368/// ```
369///
370/// ## Secondary frame sets
371///
372/// It works in the same way as the main frame, but requires a unique
373/// name to identify this frame set:
374///
375/// ```no_run
376/// # use tracy_gizmos::*;
377/// # fn update() { todo!() }
378/// # fn render() { todo!() }
379/// # fn update_bots() { todo!() }
380/// let ai = std::thread::spawn(|| {
381/// loop {
382/// update_bots();
383/// frame!("ai");
384/// }
385/// });
386///
387/// loop {
388/// update();
389/// render();
390/// frame!();
391/// }
392/// ```
393///
394/// ## Discontinuous frames
395///
396/// As discontinuous frame doesn't start immediately after previous
397/// one has ended, you need to manually mark the frame's scope:
398///
399/// ```no_run
400/// # use tracy_gizmos::*;
401/// fn do_io_request() {
402/// // This declares the `_io` variable containing a guard, which
403/// // marks the frame end when dropped.
404/// frame!(_io, "IO");
405///
406/// // do the I/O work.
407/// }
408/// ```
409#[macro_export]
410#[cfg(any(doc, feature = "enabled"))]
411macro_rules! frame {
412 () => {
413 // SAFETY: Null pointer means main frame.
414 unsafe {
415 $crate::details::mark_frame_end(std::ptr::null());
416 }
417 };
418
419 ($name:literal) => {
420 // SAFETY: We null-terminate the string.
421 unsafe {
422 $crate::details::mark_frame_end(concat!($name, '\0').as_ptr());
423 }
424 };
425
426 ($var:ident, $name:literal) => {
427 #[allow(unused_variables)]
428 // SAFETY: We null-terminate the string.
429 let $var = unsafe {
430 $crate::details::discontinuous_frame(concat!($name, '\0').as_ptr().cast())
431 };
432 };
433}
434
435#[macro_export]
436#[cfg(all(not(doc), not(feature = "enabled")))]
437macro_rules! frame {
438 ($($name:literal)? $($var:ident, $n:literal)?) => {
439 // $var could be used to denote a lexically scoped frame or
440 // even be manually `drop`-ed. Hence, we need to define it to
441 // keep the macro-using code compilable.
442 $(
443 #[allow(unused_variables)]
444 let $var = $crate::Frame();
445 )?
446 };
447}
448
449#[cfg(feature = "enabled")]
450static STARTED: AtomicBool = AtomicBool::new(false);
451
452/// Starts the Tracy capture.
453///
454/// Must be called *before* any other Tracy usage.
455///
456/// # Panics
457///
458/// Only one active capture can exist. Hence any consecutive
459/// `start_capture()` will panic, unless previously started capture is
460/// dropped.
461///
462/// # Examples
463///
464/// ```no_run
465/// let _tracy = tracy_gizmos::start_capture();
466/// ```
467pub fn start_capture() -> TracyCapture {
468 #[cfg(feature = "enabled")]
469 {
470 if STARTED.swap(true, Ordering::Acquire) {
471 panic!("Tracy capture has been started already.");
472 }
473 // SAFETY: Check above ensures this happens once.
474 unsafe {
475 sys::___tracy_startup_profiler();
476 }
477 }
478
479 TracyCapture(PhantomData)
480}
481
482/// Represents an active Tracy capture.
483///
484/// Obtaining a [`TracyCapture`] is *required* to instrument the code.
485/// Otherwise, the behaviour of instrumenting is undefined.
486///
487/// It is not allowed to have multiple copies of the [`TracyCapture`].
488///
489/// When it is dropped, the Tracy connection will be shutdown, which
490/// will also finish the capture.
491pub struct TracyCapture(PhantomData<*mut ()>);
492
493impl TracyCapture {
494 /// Returns `true` if a connection is currently established with
495 /// the Tracy server.
496 ///
497 /// # Examples
498 ///
499 /// This method can be used to ensure the profiler connection
500 /// _before_ doing any profiled work.
501 ///
502 /// ```no_run
503 /// let tracy = tracy_gizmos::start_capture();
504 /// while !tracy.is_connected() {
505 /// std::thread::yield_now();
506 /// }
507 /// // You can do the profiling here knowing it will reach
508 /// // Tracy.
509 /// ```
510 ///
511 /// You can also enabled `no-exit` feature instead, so
512 /// [`TracyCapture`] will do a blocking wait for the profiling data
513 /// to be transfered to the server, when dropped.
514 pub fn is_connected(&self) -> bool {
515 #[cfg(feature = "enabled")]
516 // SAFETY: self could exist only if startup was issued and
517 // succeeded.
518 unsafe {
519 sys::___tracy_connected() != 0
520 }
521
522 #[cfg(not(feature = "enabled"))]
523 true
524 }
525}
526
527#[cfg(feature = "enabled")]
528impl Drop for TracyCapture {
529 fn drop(&mut self) {
530 // SAFETY: self could exist only if startup was issued and
531 // succeeded.
532 unsafe {
533 sys::___tracy_shutdown_profiler();
534 }
535 STARTED.store(false, Ordering::Release);
536 }
537}
538
539/// Instruments the current scope with a profiling zone.
540///
541/// A zone represents the lifetime of a special on-stack profiler
542/// variable. Typically, it would exist for the duration of a whole
543/// profiled function scope, but it is also possible to measure time
544/// spent in nested loops or if branches.
545///
546/// Under the hood it declares a local [`Zone`].
547///
548/// A custom name is required for the zone, which allows to identify
549/// it later in the Trace visualization.
550///
551/// This will automatically record source file name, and location.
552///
553/// # Examples
554///
555/// Just adding a profiling zone is as easy as:
556///
557/// ```no_run
558/// # use tracy_gizmos::*;
559/// fn do_stuff() {
560/// zone!("stuff");
561/// // now actually do stuff :-)
562/// }
563/// ```
564///
565/// Optionally, a custom [`Color`] could be assigned for to zone.
566/// Note, that the color value will be constant in the recording.
567///
568/// ```no_run
569/// # use tracy_gizmos::*;
570/// fn do_stuff() {
571/// zone!("stuff", Color::BISQUE);
572/// // now actually do stuff :-)
573/// }
574/// ```
575///
576/// ## Nesting
577///
578/// Multiple active zones can exist and they will be nested in
579/// parent-child relationship automatically by Tracy.
580///
581/// ```no_run
582/// # use tracy_gizmos::*;
583/// # fn work() {}
584/// fn work_cycle(times: usize) {
585/// zone!("doing work");
586/// for _ in 0..times {
587/// zone!("unit of work");
588/// work();
589/// }
590/// }
591/// ```
592///
593/// ## Filtering zones
594///
595/// Zone logging can be disabled on a per-zone basis:
596///
597/// ```no_run
598/// # use tracy_gizmos::*;
599/// const PROFILE_JOBS: bool = false;
600/// zone!("Do Jobs", enabled: PROFILE_JOBS); // no runtime cost.
601/// ```
602///
603/// Note that this parameter may be a run-time expression, which
604/// should evaluate to `bool`, such as a user-controller switch to
605/// enable the profiling of a specific part of the code only when
606/// needed.
607///
608/// ```no_run
609/// # use tracy_gizmos::*;
610/// # fn toggled() -> bool { true }
611/// # let mut do_profiles = false;
612/// if toggled() {
613/// do_profiles = !do_profiles;
614/// }
615///
616/// zone!("Do Jobs", enabled: do_profiles); // runtime cost.
617/// ```
618///
619/// ## Dynamic data
620///
621/// It is possible to have a set of dynamic data attached to the
622/// particular zone. Refer to [`Zone`] for more details.
623///
624/// ```no_run
625/// # use tracy_gizmos::*;
626/// # let file_path = "./main.rs";
627/// zone!(parsing, "Parsing");
628/// parsing.text(file_path);
629/// ```
630#[macro_export]
631#[cfg(any(doc, feature = "enabled"))]
632macro_rules! zone {
633 ( $name:literal) => { $crate::zone!(_z, $name, $crate::Color::UNSPECIFIED, enabled:true) };
634 ($var:ident, $name:literal) => { $crate::zone!($var, $name, $crate::Color::UNSPECIFIED, enabled:true) };
635 ( $name:literal, $color:expr) => { $crate::zone!(_z, $name, $color, enabled:true) };
636 ($var:ident, $name:literal, $color:expr) => { $crate::zone!($var, $name, $color, enabled:true) };
637 ( $name:literal, enabled:$e:expr) => { $crate::zone!(_z, $name, $crate::Color::UNSPECIFIED, enabled:$e) };
638 ($var:ident, $name:literal, enabled:$e:expr) => { $crate::zone!($var, $name, $crate::Color::UNSPECIFIED, enabled:$e) };
639 ( $name:literal, $color:expr, enabled:$e:expr) => { $crate::zone!(_z, $name, $color, enabled:$e) };
640 ($var:ident, $name:literal, $color:expr, enabled:$e:expr) => {
641 #[allow(unused_variables)]
642 // SAFETY: This macro ensures that location & context data are correct.
643 let $var = unsafe {
644 $crate::details::zone($crate::zone!(@loc $name, $color), if $e {1} else {0})
645 };
646 };
647
648 (@loc $name:literal, $color: expr) => {{
649 // This is an implementation detail and can be changed at any moment.
650
651 #[cfg(feature = "unstable-function-names")]
652 struct X;
653 #[cfg(feature = "unstable-function-names")]
654 const FUNCTION: &'static [u8] = {
655 &$crate::details::get_fn_name_from_nested_type::<X>()
656 };
657
658 #[cfg(not(feature = "unstable-function-names"))]
659 const FUNCTION: &'static [u8] = b"<unavailable>\0";
660
661 // SAFETY: All passed data is created here and is correct.
662 static LOC: $crate::ZoneLocation = unsafe {
663 $crate::details::zone_location(
664 concat!($name, '\0'),
665 FUNCTION,
666 concat!(file!(), '\0'),
667 line!(),
668 $crate::Color::as_u32(&$color),
669 )
670 };
671 &LOC
672 }};
673}
674
675#[macro_export]
676#[cfg(all(not(doc), not(feature = "enabled")))]
677macro_rules! zone {
678 ($($var:ident,)? $name:literal, enabled:$e:expr) => {
679 // Silences unused enabled expression warning.
680 _ = $e;
681 $crate::zone!($($var,)? $name, (), enabled:$e);
682 };
683
684 ($($var:ident,)? $name:literal $(,$color:expr)? $(,enabled:$e:expr)?) => {
685 // $var could be used to add dynamic zone data, so we need to
686 // define it to keep the macro-using code compilable.
687 $(
688 #[allow(unused_variables)]
689 let $var = $crate::Zone::new();
690 )?
691 // Silences unused `Color` import warning.
692 $(
693 _ = $color;
694 )?
695 };
696}
697
698/// Profiling zone.
699///
700/// Refer to [`zone!`] for the usage how-to.
701///
702/// It instruments the current scope. Hence, the profiling zone will
703/// end when [`Zone`] is dropped.
704pub struct Zone {
705 #[cfg(feature = "enabled")]
706 ctx: sys::TracyCZoneCtx,
707 _unsend: PhantomData<*mut ()>,
708}
709
710#[cfg(any(doc, feature = "enabled"))]
711impl Drop for Zone {
712 #[inline(always)]
713 fn drop(&mut self) {
714 #[cfg(feature = "enabled")]
715 // SAFETY: The only way to have Zone is to construct it via
716 // zone! macro, which ensures that ctx value is correct.
717 unsafe {
718 sys::___tracy_emit_zone_end(self.ctx);
719 }
720 }
721}
722
723impl Zone {
724 #[doc(hidden)]
725 #[cfg(not(feature = "enabled"))]
726 pub fn new() -> Self {
727 Self { _unsend: PhantomData }
728 }
729
730 /// Allows to control the zone color dynamically.
731 ///
732 /// This can be called multiple times, however only the latest
733 /// call will have an effect.
734 pub fn color(&self, color: Color) {
735 #[cfg(feature = "enabled")]
736 // SAFETY: self always contains a valid `ctx`.
737 unsafe {
738 sys::___tracy_emit_zone_color(self.ctx, color.as_u32());
739 }
740 #[cfg(not(feature = "enabled"))]
741 {
742 // Silences unused import warning.
743 _ = color;
744 }
745 }
746
747 /// Adds a custom numeric value that will be displayed along with
748 /// the zone information. E.g. a loop iteration or size of the
749 /// processed buffer.
750 ///
751 /// This method can be called multiple times, all of the passed
752 /// values will be attached to the zone matching the call order.
753 #[cfg(feature = "enabled")]
754 pub fn number(&self, value: u64) {
755 #[cfg(feature = "enabled")]
756 // SAFETY: self always contains a valid `ctx`.
757 unsafe {
758 sys::___tracy_emit_zone_value(self.ctx, value);
759 }
760 }
761
762 /// Adds a custom text string that will be displayed along with
763 /// the zone information. E.g. name of the file you are
764 /// processing.
765 ///
766 /// The profiler will allocate a temporary internal buffer and
767 /// copy the passed value there. Hence, this operation involves a
768 /// small run-time cost.
769 ///
770 /// This method can be called multiple times, all of the passed
771 /// values will be attached to the zone matching the call order.
772 ///
773 /// Be aware that the passed text slice couldn't be larger than 64
774 /// Kb.
775 pub fn text(&self, s: &str) {
776 #[cfg(feature = "enabled")]
777 {
778 debug_assert!(s.len() < u16::MAX as usize);
779 // SAFETY: self always contains a valid `ctx`.
780 unsafe {
781 sys::___tracy_emit_zone_text(self.ctx, s.as_ptr().cast(), s.len())
782 }
783 }
784 }
785}
786
787/// A statically allocated location for a profiling zone.
788///
789/// It is an implementation detail and can be changed at any moment.
790#[doc(hidden)]
791#[repr(transparent)]
792pub struct ZoneLocation(#[cfg(feature = "enabled")] sys::___tracy_source_location_data);
793
794// SAFETY: It is fully static and constant.
795unsafe impl Send for ZoneLocation {}
796unsafe impl Sync for ZoneLocation {}
797
798/// Discontinuous frame.
799///
800/// Refer to [`frame!`] for usage howto.
801///
802/// It instruments the current frame scope. Hence, the discontinuous
803/// frame will be marked as finished when [`Frame`] is dropped.
804pub struct Frame(#[cfg(feature = "enabled")] *const i8);
805
806#[cfg(any(doc, feature = "enabled"))]
807impl Drop for Frame {
808 #[inline(always)]
809 fn drop(&mut self) {
810 #[cfg(feature = "enabled")]
811 // SAFETY: The only way to have Frame is to construct it via
812 // frame! macro, which ensures that contained pointer is
813 // correct.
814 unsafe {
815 sys::___tracy_emit_frame_mark_end(self.0);
816 }
817 }
818}
819
820/// Tracy can collect additional information about the profiled
821/// application, which will be available in the trace description.
822/// This can include data such as the source repository revision,
823/// crate version, application's environment, etc.
824///
825/// This can be called multiple times. Tracy will accumulate all the
826/// information and display it altogether.
827///
828/// Be aware that the passed text slice couldn't be larger than 64
829/// Kb.
830///
831/// ```no_run
832/// # use tracy_gizmos::*;
833/// app_info("My fancy application");
834/// app_info(env!("CARGO_PKG_VERSION"));
835/// ```
836#[inline(always)]
837pub fn app_info(info: &str) {
838 #[cfg(feature = "enabled")]
839 {
840 debug_assert!(info.len() < u16::MAX as usize);
841 // SAFETY: Slice should contain valid data and having no
842 // terminating zero is fine.
843 unsafe {
844 sys::___tracy_emit_message_appinfo(info.as_ptr().cast(), info.len());
845 }
846 }
847}
848
849/// Implementation details, do not relay on anything from this module!
850///
851/// It is public only due to the usage in public macro bodies.
852#[doc(hidden)]
853#[cfg(feature = "enabled")]
854pub mod details {
855 use std::ffi::c_void;
856 use super::*;
857
858 #[inline(always)]
859 pub const unsafe fn zone_location(
860 name: &'static str,
861 func: &'static [u8],
862 file: &'static str,
863 line: u32,
864 color: u32,
865 ) -> ZoneLocation {
866 ZoneLocation(
867 sys::___tracy_source_location_data {
868 name: name.as_ptr().cast(),
869 function: func.as_ptr().cast(),
870 file: file.as_ptr().cast(),
871 line,
872 color,
873 }
874 )
875 }
876
877 #[inline(always)]
878 pub unsafe fn zone(location: &ZoneLocation, enabled: i32) -> Zone {
879 let ctx = sys::___tracy_emit_zone_begin(&location.0, enabled);
880 Zone { ctx, _unsend: PhantomData }
881 }
882
883 #[inline(always)]
884 pub unsafe fn set_thread_name(name: *const u8) {
885 sys::___tracy_set_thread_name(name.cast());
886 }
887
888 #[inline(always)]
889 pub unsafe fn message(text: *const u8) {
890 sys::___tracy_emit_messageL(
891 text.cast(),
892 0, // callstack depth, 0 is disabled.
893 );
894 }
895
896 #[inline(always)]
897 pub fn message_size(text: &str) {
898 debug_assert!(text.len() < u16::MAX as usize);
899 // SAFETY: Dynamic non-zero-terminated string is fine.
900 unsafe {
901 sys::___tracy_emit_message(
902 text.as_ptr().cast(),
903 text.len(),
904 0, // callstack depth, 0 is disabled.
905 );
906 }
907 }
908
909 #[inline(always)]
910 pub fn message_size_color(text: &str, color: Color) {
911 debug_assert!(text.len() < u16::MAX as usize);
912 // SAFETY: Dynamic non-zero-terminated string is fine.
913 unsafe {
914 sys::___tracy_emit_messageC(
915 text.as_ptr().cast(),
916 text.len(),
917 color.as_u32(),
918 0, // callstack depth, 0 is disabled.
919 );
920 }
921 }
922
923 #[inline(always)]
924 pub unsafe fn message_color(text: *const u8, color: Color) {
925 sys::___tracy_emit_messageLC(
926 text.cast(),
927 color.as_u32(),
928 0, // callstack depth, 0 is disabled.
929 );
930 }
931
932 #[inline(always)]
933 pub unsafe fn mark_frame_end(name: *const u8) {
934 sys::___tracy_emit_frame_mark(name.cast());
935 }
936
937 #[inline(always)]
938 pub unsafe fn discontinuous_frame(name: *const i8) -> Frame {
939 sys::___tracy_emit_frame_mark_start(name);
940 Frame(name)
941 }
942
943 #[inline(always)]
944 pub unsafe fn track_alloc<T>(name: *const u8, ptr: *const T, size: usize) {
945 track_alloc_impl(name, ptr.cast(), size);
946 }
947
948 #[inline(always)]
949 unsafe fn track_alloc_impl(name: *const u8, ptr: *const c_void, size: usize) {
950 sys::___tracy_emit_memory_alloc_named(ptr, size, 0, name.cast());
951 }
952
953 #[inline(always)]
954 pub unsafe fn track_free<T>(name: *const u8, ptr: *const T) {
955 track_free_impl(name, ptr.cast());
956 }
957
958 #[inline(always)]
959 unsafe fn track_free_impl(name: *const u8, ptr: *const c_void) {
960 sys::___tracy_emit_memory_free_named(ptr, 0, name.cast());
961 }
962
963 // Function name trick only works with an unstable
964 // feature, which provides const `type_name`. Tracking
965 // issue on the Rust side:
966 // https://github.com/rust-lang/rust/issues/63084
967 #[cfg(feature = "unstable-function-names")]
968 pub const fn get_fn_name_from_nested_type<T>() -> [u8; std::any::type_name::<T>().len() - 2]
969 where
970 [(); std::any::type_name::<T>().len() - 2]:
971 {
972 let bytes = std::any::type_name::<T>().as_bytes();
973 // We skip (-3 + 1) of the type name length, to skip the '::X' suffix and add the terminating zero.
974 let mut buf = [0; std::any::type_name::<T>().len() - 2];
975 let n = buf.len() - 1;
976 let mut i = 0;
977
978 while i < n {
979 buf[i] = bytes[i];
980 i += 1;
981 }
982
983 buf
984 }
985
986 // Function above could be replaced by the following code directly
987 // in the macro body, when const_type_name is stable.
988 //
989 // const FUNCTION: &'static [u8] = {
990 // struct X;
991 // const TYPE_NAME: &'static str = std::any::type_name::<X>();
992 // // We skip 3 of the '::X' suffix and add 1 for the terminating zero.
993 // const FUNCTION_LEN: usize = TYPE_NAME.len() - 3 + 1;
994 // &$crate::details::as_array::<FUNCTION_LEN>(TYPE_NAME)
995 // };
996 // pub const fn as_array<const N: usize>(s: &'static str) -> [u8; N] {
997 // let bytes = s.as_bytes();
998 // let mut buf = [0; N];
999 // let mut i = 0;
1000 // while i < N - 1 {
1001 // buf[i] = bytes[i];
1002 // i += 1;
1003 // }
1004 // buf
1005 // }
1006}
1007
1008#[cfg(test)]
1009mod tests {
1010 #[cfg(feature = "enabled")]
1011 use super::*;
1012
1013 #[cfg(feature = "enabled")]
1014 #[test]
1015 fn double_lifecycle() {
1016 let tracy = start_capture();
1017 drop(tracy);
1018 let _tracy = start_capture();
1019 }
1020
1021 #[cfg(feature = "enabled")]
1022 #[test]
1023 #[should_panic]
1024 fn double_start_fails() {
1025 let _tracy1 = start_capture();
1026 let _tracy2 = start_capture();
1027 }
1028}