Vendor things

This commit is contained in:
John Doty 2024-03-08 11:03:01 -08:00
parent 5deceec006
commit 977e3c17e5
19434 changed files with 10682014 additions and 0 deletions

View file

@ -0,0 +1,149 @@
use crate::Client;
/// A non-continuous frame region.
///
/// Create with the [`Client::non_continuous_frame`] function.
pub struct Frame(Client, FrameName);
/// A name for secondary and non-continuous frames.
///
/// Create with the [`frame_name!`] macro.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct FrameName(pub(crate) &'static str);
/// Instrumentation for global frame indicators.
impl Client {
/// Indicate that rendering of a continuous frame has ended.
///
/// # Examples
///
/// In a traditional rendering scenarios a frame mark should be inserted after a buffer swap.
///
/// ```
/// use tracy_client::Client;
/// # fn swap_buffers() {}
/// # let client = tracy_client::Client::start();
/// // loop {
/// // ...
/// swap_buffers();
/// Client::running().expect("client must be running").frame_mark();
/// // }
/// ```
pub fn frame_mark(&self) {
#[cfg(feature = "enable")]
unsafe {
sys::___tracy_emit_frame_mark(std::ptr::null());
}
}
/// Indicate that rendering of a secondary (named) continuous frame has ended.
///
/// # Examples
///
/// Much like with the primary frame mark, the secondary (named) frame mark should be inserted
/// after some continuously repeating operation finishes one iteration of its processing.
///
/// ```
/// use tracy_client::frame_name;
/// # fn physics_tick() {}
/// # let client = tracy_client::Client::start();
/// // loop {
/// // ...
/// physics_tick();
/// tracy_client::Client::running()
/// .expect("client must be running")
/// .secondary_frame_mark(frame_name!("physics"));
/// // }
/// ```
pub fn secondary_frame_mark(&self, name: FrameName) {
#[cfg(feature = "enable")]
unsafe {
// SAFE: We ensured that the name would be null-terminated.
sys::___tracy_emit_frame_mark(name.0.as_ptr().cast());
}
}
/// Indicate that a processing of a non-continuous frame has begun.
///
/// Dropping the returned [`Frame`] will terminate the non-continuous frame.
///
/// # Examples
///
/// ```
/// use tracy_client::frame_name;
/// # let client = tracy_client::Client::start();
/// tracy_client::Client::running()
/// .expect("client must be running")
/// .non_continuous_frame(frame_name!("a frame"));
/// ```
pub fn non_continuous_frame(&self, name: FrameName) -> Frame {
#[cfg(feature = "enable")]
unsafe {
// SAFE: We ensure that the name would be null-terminated.
sys::___tracy_emit_frame_mark_start(name.0.as_ptr().cast());
}
Frame(self.clone(), name)
}
}
/// Construct a [`FrameName`].
///
/// The resulting value may be used as an argument for the the [`Client::secondary_frame_mark`] and
/// [`Client::non_continuous_frame`] methods. The macro can be used in a `const` context.
#[macro_export]
macro_rules! frame_name {
($name: literal) => {{
unsafe { $crate::internal::create_frame_name(concat!($name, "\0")) }
}};
}
impl Drop for Frame {
fn drop(&mut self) {
#[cfg(feature = "enable")]
unsafe {
// SAFE: We ensure that thena me would be null-terminated. We also still have an owned
// Client handle.
sys::___tracy_emit_frame_mark_end(self.1 .0.as_ptr().cast());
std::convert::identity(&self.0);
}
}
}
/// Convenience shortcut for [`Client::frame_mark`] on the current client.
///
/// # Panics
///
/// - If a `Client` isn't currently running.
pub fn frame_mark() {
Client::running()
.expect("frame_mark! without a running Client")
.frame_mark();
}
/// Convenience macro for [`Client::secondary_frame_mark`] on the current client.
///
/// # Panics
///
/// - If a `Client` isn't currently running.
#[macro_export]
macro_rules! secondary_frame_mark {
($name: literal) => {{
$crate::Client::running()
.expect("secondary_frame_mark! without a running Client")
.secondary_frame_mark($crate::frame_name!($name))
}};
}
/// Convenience macro for [`Client::non_continuous_frame`] on the current client.
///
/// # Panics
///
/// - If a `Client` isn't currently running.
#[macro_export]
macro_rules! non_continuous_frame {
($name: literal) => {{
$crate::Client::running()
.expect("non_continuous_frame! without a running Client")
.non_continuous_frame($crate::frame_name!($name))
}};
}

View file

@ -0,0 +1,281 @@
#![deny(unsafe_op_in_unsafe_fn, missing_docs)]
#![cfg_attr(not(feature = "enable"), allow(unused_variables, unused_imports))]
//! This crate is a set of safe bindings to the client library of the [Tracy profiler].
//!
//! If you have already instrumented your application with `tracing`, consider the `tracing-tracy`
//! crate.
//!
//! [Tracy profiler]: https://github.com/wolfpld/tracy
//!
//! # Important note
//!
//! Depending on the configuration Tracy may broadcast discovery packets to the local network and
//! expose the data it collects in the background to that same network. Traces collected by Tracy
//! may include source and assembly code as well.
//!
//! As thus, you may want make sure to only enable the `tracy-client` crate conditionally, via
//! the `enable` feature flag provided by this crate.
//!
//! # Features
//!
//! The following crate features are provided to customize the functionality of the Tracy client:
//!
#![doc = include_str!("../FEATURES.mkd")]
#![cfg_attr(tracy_client_docs, feature(doc_auto_cfg))]
pub use crate::frame::{frame_mark, Frame, FrameName};
pub use crate::plot::PlotName;
pub use crate::span::{Span, SpanLocation};
use std::alloc;
use std::ffi::CString;
pub use sys;
mod frame;
mod plot;
mod span;
mod state;
/// /!\ /!\ Please don't rely on anything in this module T_T /!\ /!\
#[doc(hidden)]
pub mod internal {
pub use crate::{span::SpanLocation, sys};
pub use once_cell::sync::Lazy;
pub use std::any::type_name;
pub use std::ptr::null;
use std::ffi::CString;
#[inline(always)]
pub fn make_span_location(
type_name: &'static str,
span_name: *const u8,
file: *const u8,
line: u32,
) -> crate::SpanLocation {
#[cfg(feature = "enable")]
{
let function_name = CString::new(&type_name[..type_name.len() - 3]).unwrap();
crate::SpanLocation {
data: crate::sys::___tracy_source_location_data {
name: span_name.cast(),
function: function_name.as_ptr(),
file: file.cast(),
line,
color: 0,
},
_function_name: function_name,
}
}
#[cfg(not(feature = "enable"))]
crate::SpanLocation { _internal: () }
}
#[inline(always)]
pub const unsafe fn create_frame_name(name: &'static str) -> crate::frame::FrameName {
crate::frame::FrameName(name)
}
#[inline(always)]
pub const unsafe fn create_plot(name: &'static str) -> crate::plot::PlotName {
crate::plot::PlotName(name)
}
#[inline(always)]
/// Safety: `name` must be null-terminated, and a `Client` must be enabled
pub unsafe fn set_thread_name(name: *const u8) {
#[cfg(feature = "enable")]
unsafe {
sys::___tracy_set_thread_name(name.cast())
}
}
}
/// A type representing an enabled Tracy client.
///
/// Obtaining a `Client` is required in order to instrument the application.
///
/// Multiple copies of a Client may be live at once. As long as at least one `Client` value lives,
/// the `Tracy` client is enabled globally. In addition to collecting information through the
/// instrumentation inserted by you, the Tracy client may automatically collect information about
/// execution of the program while it is enabled. All this information may be stored in memory
/// until a profiler application connects to the client to read the data.
///
/// Depending on the build configuration, the client may collect and make available machine
/// and source code of the application as well as other potentially sensitive information.
///
/// When all of the `Client` values are dropped, the underlying Tracy client will be shut down as
/// well. Shutting down the `Client` will discard any information gathered up to that point that
/// still hasn't been delivered to the profiler application.
pub struct Client(());
/// Instrumentation methods for outputting events occurring at a specific instant.
///
/// Data provided by this instrumentation can largely be considered to be equivalent to logs.
impl Client {
/// Output a message.
///
/// Specifying a non-zero `callstack_depth` will enable collection of callstack for this
/// message. The number provided will limit the number of call frames collected. Note that
/// enabling callstack collection introduces a non-trivial amount of overhead to this call.
pub fn message(&self, message: &str, callstack_depth: u16) {
#[cfg(feature = "enable")]
unsafe {
let stack_depth = adjust_stack_depth(callstack_depth).into();
sys::___tracy_emit_message(message.as_ptr().cast(), message.len(), stack_depth)
}
}
/// Output a message with an associated color.
///
/// Specifying a non-zero `callstack_depth` will enable collection of callstack for this
/// message. The number provided will limit the number of call frames collected. Note that
/// enabling callstack collection introduces a non-trivial amount of overhead to this call.
///
/// The colour shall be provided as RGBA, where the least significant 8 bits represent the alpha
/// component and most significant 8 bits represent the red component.
pub fn color_message(&self, message: &str, rgba: u32, callstack_depth: u16) {
#[cfg(feature = "enable")]
unsafe {
let depth = adjust_stack_depth(callstack_depth).into();
sys::___tracy_emit_messageC(message.as_ptr().cast(), message.len(), rgba >> 8, depth)
}
}
}
impl Client {
/// Set the current thread name to the provided value.
///
/// # Panics
///
/// This function will panic if the name contains interior null characters.
pub fn set_thread_name(&self, name: &str) {
#[cfg(feature = "enable")]
unsafe {
let name = CString::new(name).unwrap();
// SAFE: `name` is a valid null-terminated string.
internal::set_thread_name(name.as_ptr().cast());
}
}
}
/// Convenience macro for [`Client::set_thread_name`] on the current client.
///
/// Note that any interior null characters terminate the name early. This is not checked for.
///
/// # Panics
///
/// - If a `Client` isn't currently running.
#[macro_export]
macro_rules! set_thread_name {
($name: literal) => {{
$crate::Client::running().expect("set_thread_name! without a running Client");
unsafe {
// SAFE: `name` is a valid null-terminated string.
$crate::internal::set_thread_name(concat!($name, "\0").as_ptr().cast())
}
}};
}
/// A profiling wrapper around another allocator.
///
/// See documentation for [`std::alloc`](std::alloc) for more information about global allocators.
///
/// Note that this wrapper will start up the client on the first allocation, if not enabled
/// already.
///
/// # Examples
///
/// In your executable, add:
///
/// ```rust
/// # use tracy_client::*;
/// #[global_allocator]
/// static GLOBAL: ProfiledAllocator<std::alloc::System> =
/// ProfiledAllocator::new(std::alloc::System, 100);
/// ```
pub struct ProfiledAllocator<T>(T, u16);
impl<T> ProfiledAllocator<T> {
/// Construct a new `ProfiledAllocator`.
///
/// Specifying a non-zero `callstack_depth` will enable collection of callstack for this
/// message. The number provided will limit the number of call frames collected. Note that
/// enabling callstack collection introduces a non-trivial amount of overhead to each
/// allocation and deallocation.
pub const fn new(inner_allocator: T, callstack_depth: u16) -> Self {
Self(inner_allocator, adjust_stack_depth(callstack_depth))
}
fn emit_alloc(&self, ptr: *mut u8, size: usize) {
#[cfg(feature = "enable")]
unsafe {
Client::start();
if self.1 == 0 {
sys::___tracy_emit_memory_alloc(ptr.cast(), size, 1);
} else {
sys::___tracy_emit_memory_alloc_callstack(ptr.cast(), size, self.1.into(), 1);
}
}
}
fn emit_free(&self, ptr: *mut u8) {
#[cfg(feature = "enable")]
unsafe {
if self.1 == 0 {
sys::___tracy_emit_memory_free(ptr.cast(), 1);
} else {
sys::___tracy_emit_memory_free_callstack(ptr.cast(), self.1.into(), 1);
}
}
}
}
unsafe impl<T: alloc::GlobalAlloc> alloc::GlobalAlloc for ProfiledAllocator<T> {
unsafe fn alloc(&self, layout: alloc::Layout) -> *mut u8 {
let alloc = unsafe {
// SAFE: all invariants satisfied by the caller.
self.0.alloc(layout)
};
self.emit_alloc(alloc, layout.size());
alloc
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: alloc::Layout) {
self.emit_free(ptr);
unsafe {
// SAFE: all invariants satisfied by the caller.
self.0.dealloc(ptr, layout)
}
}
unsafe fn alloc_zeroed(&self, layout: alloc::Layout) -> *mut u8 {
let alloc = unsafe {
// SAFE: all invariants satisfied by the caller.
self.0.alloc_zeroed(layout)
};
self.emit_alloc(alloc, layout.size());
alloc
}
unsafe fn realloc(&self, ptr: *mut u8, layout: alloc::Layout, new_size: usize) -> *mut u8 {
self.emit_free(ptr);
let alloc = unsafe {
// SAFE: all invariants satisfied by the caller.
self.0.realloc(ptr, layout, new_size)
};
self.emit_alloc(alloc, new_size);
alloc
}
}
/// Clamp the stack depth to the maximum supported by Tracy.
#[inline(always)]
pub(crate) const fn adjust_stack_depth(depth: u16) -> u16 {
#[cfg(windows)]
{
62 ^ ((depth ^ 62) & 0u16.wrapping_sub((depth < 62) as _))
}
#[cfg(not(windows))]
{
depth
}
}

View file

@ -0,0 +1,53 @@
use crate::Client;
/// Name of a plot.
///
/// Create with the [`plot_name!`](crate::plot_name) macro.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct PlotName(pub(crate) &'static str);
/// Instrumentation for drawing 2D plots.
impl Client {
/// Add a point with an y-axis value of `value` to the plot named `plot_name`.
///
/// # Examples
///
/// ```
/// # let client = tracy_client::Client::start();
/// tracy_client::Client::running()
/// .expect("client must be running")
/// .plot(tracy_client::plot_name!("temperature"), 37.0);
/// ```
pub fn plot(&self, plot_name: PlotName, value: f64) {
#[cfg(feature = "enable")]
unsafe {
// SAFE: We made sure the `plot` refers to a null-terminated string.
sys::___tracy_emit_plot(plot_name.0.as_ptr().cast(), value);
}
}
}
/// Construct a [`PlotName`].
///
/// The resulting value may be used as an argument for the [`Client::plot`] method. The macro can
/// be used in a `const` context.
#[macro_export]
macro_rules! plot_name {
($name: expr) => {
unsafe { $crate::internal::create_plot(concat!($name, "\0")) }
};
}
/// Convenience macro for [`Client::plot`] on the current client.
///
/// # Panics
///
/// - If a `Client` isn't currently running.
#[macro_export]
macro_rules! plot {
($name: expr, $value: expr) => {{
$crate::Client::running()
.expect("plot! without a running Client")
.plot($crate::plot_name!($name), $value)
}};
}

View file

@ -0,0 +1,275 @@
use crate::{adjust_stack_depth, Client};
use std::ffi::CString;
/// A handle representing a span of execution.
///
/// The trace span will be ended when this type is dropped.
pub struct Span {
#[cfg(feature = "enable")]
client: Client,
#[cfg(feature = "enable")]
zone: sys::___tracy_c_zone_context,
#[cfg(feature = "enable")]
_no_send_sync: std::marker::PhantomData<*mut sys::___tracy_c_zone_context>,
#[cfg(not(feature = "enable"))]
_no_send_sync: std::marker::PhantomData<*mut ()>,
}
/// A statically allocated location information for a span.
///
/// Construct with the [`span_location!`](crate::span_location) macro.
pub struct SpanLocation {
#[cfg(feature = "enable")]
pub(crate) _function_name: CString,
#[cfg(feature = "enable")]
pub(crate) data: sys::___tracy_source_location_data,
#[cfg(not(feature = "enable"))]
pub(crate) _internal: (),
}
unsafe impl Send for SpanLocation {}
unsafe impl Sync for SpanLocation {}
/// Instrumentation for timed regions, spans or zones of execution.
impl Client {
/// Start a new Tracy span/zone.
///
/// In order to obtain a [`SpanLocation`] value to provide to this function use the
/// [`span_location!`](crate::span_location) macro.
///
/// Specifying a non-zero `callstack_depth` will enable collection of callstack for this
/// message. The number provided will limit the number of call frames collected. Note that
/// enabling callstack collection introduces a non-trivial amount of overhead to this call. On
/// some systems this value may be clamped to a maximum value supported by the target.
///
/// The [`span!`](crate::span!) macro is a convenience wrapper over this method.
///
/// # Example
///
/// In the following example the span is created with the location at which the
/// `span_location!` macro appears and will measure the execution of the 100ms long sleep.
///
/// ```rust
/// use tracy_client::{Client, span_location};
/// let client = Client::start();
/// {
/// let _span = client.span(span_location!("sleeping"), 100);
/// std::thread::sleep(std::time::Duration::from_millis(100));
/// } // _span ends
/// ```
#[inline]
pub fn span(self, loc: &'static SpanLocation, callstack_depth: u16) -> Span {
#[cfg(feature = "enable")]
unsafe {
let zone = if callstack_depth == 0 {
sys::___tracy_emit_zone_begin(&loc.data, 1)
} else {
let stack_depth = adjust_stack_depth(callstack_depth).into();
sys::___tracy_emit_zone_begin_callstack(&loc.data, stack_depth, 1)
};
Span {
client: self,
zone,
_no_send_sync: std::marker::PhantomData,
}
}
#[cfg(not(feature = "enable"))]
Span {
_no_send_sync: std::marker::PhantomData,
}
}
/// Start a new Tracy span/zone.
///
/// This function allocates the span information on the heap until it is read out by the
/// profiler. Prefer the [`Client::span`] as a allocation-free and faster alternative when
/// possible.
///
/// Specifying a non-zero `callstack_depth` will enable collection of callstack for this
/// message. The number provided will limit the number of call frames collected. Note that
/// enabling callstack collection introduces a non-trivial amount of overhead to this call. On
/// some systems this value may be clamped to a maximum value supported by the target.
///
/// # Example
///
/// In the following example the span is created with custom span source data and will measure
/// the execution of the 100ms long sleep.
///
/// ```rust
/// use tracy_client::Client;
/// let client = Client::start();
/// {
/// let _span = client.span_alloc(Some("hello"), "my_function", "hello.rs", 42, 100);
/// std::thread::sleep(std::time::Duration::from_millis(100));
/// } // _span ends
/// ```
#[inline]
pub fn span_alloc(
self,
name: Option<&str>,
function: &str,
file: &str,
line: u32,
callstack_depth: u16,
) -> Span {
#[cfg(feature = "enable")]
unsafe {
let loc = sys::___tracy_alloc_srcloc_name(
line,
file.as_ptr().cast(),
file.len(),
function.as_ptr().cast(),
function.len(),
name.map(|n| n.as_ptr().cast()).unwrap_or(std::ptr::null()),
name.unwrap_or("").len(),
);
let zone = if callstack_depth == 0 {
sys::___tracy_emit_zone_begin_alloc(loc, 1)
} else {
let stack_depth = adjust_stack_depth(callstack_depth).into();
sys::___tracy_emit_zone_begin_alloc_callstack(loc, stack_depth, 1)
};
Span {
client: self,
zone,
_no_send_sync: std::marker::PhantomData,
}
}
#[cfg(not(feature = "enable"))]
Span {
_no_send_sync: std::marker::PhantomData,
}
}
}
impl Span {
/// Emit a numeric value associated with this span.
pub fn emit_value(&self, value: u64) {
#[cfg(feature = "enable")]
unsafe {
// SAFE: the only way to construct `Span` is by creating a valid tracy zone context.
sys::___tracy_emit_zone_value(self.zone, value);
}
}
/// Emit some text associated with this span.
pub fn emit_text(&self, text: &str) {
#[cfg(feature = "enable")]
unsafe {
// SAFE: the only way to construct `Span` is by creating a valid tracy zone context.
sys::___tracy_emit_zone_text(self.zone, text.as_ptr().cast(), text.len());
}
}
/// Emit a color associated with this span.
pub fn emit_color(&self, color: u32) {
#[cfg(feature = "enable")]
unsafe {
// SAFE: the only way to construct `Span` is by creating a valid tracy zone context.
// TODO: verify if we need to shift by 8 or not...?
sys::___tracy_emit_zone_color(self.zone, color);
}
}
}
impl Drop for Span {
fn drop(&mut self) {
#[cfg(feature = "enable")]
unsafe {
// SAFE: The only way to construct `Span` is by creating a valid tracy zone context. We
// also still have an owned Client handle.
sys::___tracy_emit_zone_end(self.zone);
std::convert::identity(&self.client);
}
}
}
/// Construct a <code>&'static [SpanLocation]</code>.
///
/// The returned `SpanLocation` is allocated statically and is cached between invocations. This
/// `SpanLocation` will refer to the file and line at which this macro has been invoked, as well as
/// to the item containing this macro invocation.
///
/// The resulting value may be used as an argument for the [`Client::span`] method.
///
/// # Example
///
/// ```rust
/// let location: &'static tracy_client::SpanLocation = tracy_client::span_location!("some name");
/// ```
#[macro_export]
macro_rules! span_location {
() => {{
struct S;
// String processing in `const` when, Oli?
static LOC: $crate::internal::Lazy<$crate::internal::SpanLocation> =
$crate::internal::Lazy::new(|| {
$crate::internal::make_span_location(
$crate::internal::type_name::<S>(),
$crate::internal::null(),
concat!(file!(), "\0").as_ptr(),
line!(),
)
});
&*LOC
}};
($name: expr) => {{
struct S;
// String processing in `const` when, Oli?
static LOC: $crate::internal::Lazy<$crate::internal::SpanLocation> =
$crate::internal::Lazy::new(|| {
$crate::internal::make_span_location(
$crate::internal::type_name::<S>(),
concat!($name, "\0").as_ptr(),
concat!(file!(), "\0").as_ptr(),
line!(),
)
});
&*LOC
}};
}
/// Start a new Tracy span with function, file, and line determined automatically.
///
/// # Panics
///
/// `span!` will panic if the Client isn't running at the time this macro is invoked.
///
/// # Examples
///
/// Begin a span region, which will be terminated once `_span` goes out of scope:
///
/// ```
/// use tracy_client::{Client, span};
/// # let _client = tracy_client::Client::start();
/// let _span = span!("some span");
/// ```
///
/// It is also possible to enable collection of the callstack by specifying a limit of call stack
/// frames to record:
///
/// ```
/// use tracy_client::span;
/// # let _client = tracy_client::Client::start();
/// let _span = span!("some span", 32);
/// ```
///
/// Note, however, that collecting callstack introduces a non-trivial overhead at the point of
/// instrumentation.
#[macro_export]
macro_rules! span {
() => {
$crate::Client::running()
.expect("span! without a running Client")
.span($crate::span_location!(), 0)
};
($name: expr) => {
$crate::span!($name, 0)
};
($name: expr, $callstack_depth: expr) => {{
let location = $crate::span_location!($name);
$crate::Client::running()
.expect("span! without a running Client")
.span(location, $callstack_depth)
}};
}

View file

@ -0,0 +1,167 @@
use crate::Client;
use std::sync::atomic::Ordering;
/// Enabling `Tracy` when it is already enabled, or Disabling when it is already disabled will
/// cause applications to crash. I personally think it would be better if this was a sort-of
/// reference counted kind-of thing so you could enable as many times as you wish and disable
/// just as many times without any reprecursions. At the very least this could significantly
/// help tests.
///
/// We can also try to implement something like this ourselves. To do this we'd want to track 4
/// states that construct a following finite state machine:
///
/// ```text
/// 0 = disabled -> 1 = enabling
/// ^ v
/// 3 = disabling <- 2 = enabled
/// ```
///
/// And also include a reference count somewhere in there. Something we can throw in a static
/// would be ideal.
///
/// Alas, Tracy's extensive use of thread-local storage presents us with another problem we must
/// start up and shut down the client within the same thread. A most straightforward soution for
/// that would be to run a separate thread that would be dedicated entirely to just starting up and
/// shutting down the profiler.
///
/// All that seems like a major pain to implement, and so well punt on disabling entirely until
/// somebody comes with a good use-case warranting that sort of complexity.
#[cfg(feature = "enable")]
#[cfg(not(loom))]
static CLIENT_STATE: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
#[cfg(loom)]
loom::lazy_static! {
static ref CLIENT_STATE: loom::sync::atomic::AtomicUsize =
loom::sync::atomic::AtomicUsize::new(0);
}
#[cfg(feature = "enable")]
const STATE_STEP: usize = 1; // Move forward by 1 step in the FSM
#[cfg(feature = "enable")]
const STATE_DISABLED: usize = 0;
#[cfg(feature = "enable")]
const STATE_ENABLING: usize = STATE_DISABLED + STATE_STEP;
#[cfg(feature = "enable")]
const STATE_ENABLED: usize = STATE_ENABLING + STATE_STEP;
#[cfg(feature = "enable")]
#[inline(always)]
fn spin_loop() {
#[cfg(loom)]
loom::thread::yield_now();
#[cfg(not(loom))]
std::hint::spin_loop();
}
/// Client initialization and lifetime management.
impl Client {
/// Start the client.
///
/// The client must be started with this function before any instrumentation is invoked
/// anywhere in the process. This function can be called multiple times to obtain multiple
/// `Client` values.
///
/// The underying client implementation will be started up only if it wasn't already running
/// yet.
///
/// Note that there currently isn't a mechanism to stop the client once it has been started.
///
/// # Example
///
/// ```rust
/// // fn main() {
/// let _client = tracy_client::Client::start();
/// // ...
/// // }
/// ```
pub fn start() -> Self {
#[cfg(feature = "enable")]
{
let mut old_state = CLIENT_STATE.load(Ordering::Relaxed);
loop {
match old_state {
STATE_ENABLED => return Client(()),
STATE_ENABLING => {
while !Self::is_running() {
spin_loop();
}
return Client(());
}
STATE_DISABLED => {
let result = CLIENT_STATE.compare_exchange_weak(
old_state,
STATE_ENABLING,
Ordering::Relaxed,
Ordering::Relaxed,
);
if let Err(next_old_state) = result {
old_state = next_old_state;
continue;
} else {
unsafe {
// SAFE: This function must not be called if the profiler has
// already been enabled. While in practice calling this function
// multiple times will only serve to trigger an assertion, we
// cannot exactly rely on this, since it is an undocumented
// behaviour and the upstream might very well just decide to invoke
// UB instead. In the case there are multiple copies of
// `tracy-client` this invariant is not actually maintained, but
// otherwise this is sound due to the `ENABLE_STATE` that we
// manage.
//
// TODO: we _could_ define `ENABLE_STATE` in the `-sys` crate...
sys::___tracy_startup_profiler();
CLIENT_STATE.store(STATE_ENABLED, Ordering::Release);
return Client(());
}
}
}
_ => unreachable!(),
}
}
}
#[cfg(not(feature = "enable"))]
Client(())
}
/// Obtain a client handle, but only if the client is already running.
#[inline(always)]
pub fn running() -> Option<Self> {
if Self::is_running() {
Some(Client(()))
} else {
None
}
}
/// Is the client already running?
#[inline(always)]
pub fn is_running() -> bool {
#[cfg(feature = "enable")]
return CLIENT_STATE.load(Ordering::Relaxed) == STATE_ENABLED;
#[cfg(not(feature = "enable"))]
return true;
}
}
impl Clone for Client {
/// A cheaper alternative to [`Client::start`] or [`Client::running`] when there is already a
/// handle handy.
#[inline(always)]
fn clone(&self) -> Self {
// We already know that the state is `ENABLED`, no need to check.
Client(())
}
}
#[cfg(all(test, feature = "enable"))]
mod test {
use super::*;
#[test]
fn state_transitions() {
assert_eq!(0, STATE_DISABLED);
assert_eq!(STATE_DISABLED.wrapping_add(STATE_STEP), STATE_ENABLING);
assert_eq!(STATE_ENABLING.wrapping_add(STATE_STEP), STATE_ENABLED);
}
}