tracy_client/state.rs
1use crate::Client;
2
3/// Client initialization and lifetime management.
4impl Client {
5 /// Start the client.
6 ///
7 /// The client must be started with this function before any instrumentation is invoked
8 /// anywhere in the process. This function can be called multiple times to obtain multiple
9 /// `Client` values.
10 ///
11 /// The underlying client implementation will be started up only if it wasn't already running
12 /// yet.
13 ///
14 /// Note that when the `manual-lifetime` feature is used, it is a responsibility of the user
15 /// to stop `tracy` using the [`sys::___tracy_shutdown_profiler`] function. Keep in mind that
16 /// at the time this function is called there can be no other invocations to the tracy
17 /// profiler, even from other threads (or you may get a crash!)
18 ///
19 /// # Example
20 ///
21 /// ```rust
22 /// // fn main() {
23 /// let _client = tracy_client::Client::start();
24 /// // ...
25 /// // }
26 /// ```
27 pub fn start() -> Self {
28 #[cfg(not(feature = "enable"))]
29 return Self(());
30 #[cfg(all(feature = "enable", feature = "manual-lifetime"))]
31 return manual_lifetime::start();
32 #[cfg(all(feature = "enable", not(feature = "manual-lifetime")))]
33 return Self(());
34 }
35
36 /// Obtain a client handle, but only if the client is already running.
37 #[must_use]
38 pub fn running() -> Option<Self> {
39 if Self::is_running() {
40 Some(Self(()))
41 } else {
42 None
43 }
44 }
45
46 /// Is the client already running?
47 pub fn is_running() -> bool {
48 #[cfg(not(feature = "enable"))]
49 return true; // If the client is disabled, produce a "no-op" one so that users don’t need
50 // to wory about conditional use of the instrumentation in their own code.
51 #[cfg(all(feature = "enable", feature = "manual-lifetime"))]
52 return manual_lifetime::is_running();
53 #[cfg(all(feature = "enable", not(feature = "manual-lifetime")))]
54 return true; // The client is started in life-before-main (or upon first use in case of
55 // `delayed-init`
56 }
57
58 /// Is the client running and a profiler connected?
59 pub fn is_connected() -> bool {
60 #[cfg(not(feature = "enable"))]
61 return false;
62 #[cfg(feature = "enable")]
63 return Self::is_running() && unsafe { sys::___tracy_connected() != 0 };
64 }
65}
66
67impl Clone for Client {
68 /// A cheaper alternative to [`Client::start`] or [`Client::running`] when there is already a
69 /// handle handy.
70 fn clone(&self) -> Self {
71 // We already know that the state is `ENABLED`, no need to check.
72 Self(())
73 }
74}
75
76#[cfg(all(feature = "enable", feature = "manual-lifetime"))]
77mod manual_lifetime {
78 use std::sync::atomic::Ordering;
79 /// Enabling `Tracy` when it is already enabled, or Disabling when it is already disabled will
80 /// cause applications to crash. I personally think it would be better if this was a sort-of
81 /// reference counted kind-of thing so you could enable as many times as you wish and disable
82 /// just as many times without any reprecursions. At the very least this could significantly
83 /// help tests.
84 ///
85 /// We can also try to implement something like this ourselves. To do this we'd want to track 4
86 /// states that construct a following finite state machine:
87 ///
88 /// ```text
89 /// 0 = disabled -> 1 = enabling
90 /// ^ v
91 /// 3 = disabling <- 2 = enabled
92 /// ```
93 ///
94 /// And also include a reference count somewhere in there. Something we can throw in a static
95 /// would be ideal.
96 ///
97 /// Alas, Tracy's extensive use of thread-local storage presents us with another problem – we must
98 /// start up and shut down the client within the same thread. A most straightforward soution for
99 /// that would be to run a separate thread that would be dedicated entirely to just starting up and
100 /// shutting down the profiler.
101 ///
102 /// All that seems like a major pain to implement, and so we’ll punt on disabling entirely until
103 /// somebody comes with a good use-case warranting that sort of complexity.
104 #[cfg(not(loom))]
105 static CLIENT_STATE: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
106 #[cfg(loom)]
107 loom::lazy_static! {
108 static ref CLIENT_STATE: loom::sync::atomic::AtomicUsize =
109 loom::sync::atomic::AtomicUsize::new(0);
110 }
111
112 const STATE_STEP: usize = 1; // Move forward by 1 step in the FSM
113 const STATE_DISABLED: usize = 0;
114 const STATE_ENABLING: usize = STATE_DISABLED + STATE_STEP;
115 const STATE_ENABLED: usize = STATE_ENABLING + STATE_STEP;
116
117 #[inline(always)]
118 fn spin_loop() {
119 #[cfg(loom)]
120 loom::thread::yield_now();
121 #[cfg(not(loom))]
122 std::hint::spin_loop();
123 }
124
125 pub(super) fn start() -> super::Client {
126 let mut old_state = CLIENT_STATE.load(Ordering::Relaxed);
127 loop {
128 match old_state {
129 STATE_ENABLED => return super::Client(()),
130 STATE_ENABLING => {
131 while !is_running() {
132 spin_loop();
133 }
134 return super::Client(());
135 }
136 STATE_DISABLED => {
137 let result = CLIENT_STATE.compare_exchange_weak(
138 old_state,
139 STATE_ENABLING,
140 Ordering::Relaxed,
141 Ordering::Relaxed,
142 );
143 if let Err(next_old_state) = result {
144 old_state = next_old_state;
145 continue;
146 } else {
147 unsafe {
148 // SAFE: This function must not be called if the profiler has
149 // already been enabled. While in practice calling this function
150 // multiple times will only serve to trigger an assertion, we
151 // cannot exactly rely on this, since it is an undocumented
152 // behaviour and the upstream might very well just decide to invoke
153 // UB instead. In the case there are multiple copies of
154 // `tracy-client` this invariant is not actually maintained, but
155 // otherwise this is sound due to the `ENABLE_STATE` that we
156 // manage.
157 //
158 // TODO: we _could_ define `ENABLE_STATE` in the `-sys` crate...
159 let () = sys::___tracy_startup_profiler();
160 CLIENT_STATE.store(STATE_ENABLED, Ordering::Release);
161 return super::Client(());
162 }
163 }
164 }
165 _ => unreachable!(),
166 }
167 }
168 }
169
170 pub(super) fn is_running() -> bool {
171 return CLIENT_STATE.load(Ordering::Relaxed) == STATE_ENABLED;
172 }
173
174 #[cfg(test)]
175 mod test {
176 use super::*;
177
178 #[test]
179 fn state_transitions() {
180 assert_eq!(0, STATE_DISABLED);
181 assert_eq!(STATE_DISABLED.wrapping_add(STATE_STEP), STATE_ENABLING);
182 assert_eq!(STATE_ENABLING.wrapping_add(STATE_STEP), STATE_ENABLED);
183 }
184 }
185}