gdnative_core/profiler.rs
1//! Interface to Godot's built-in profiler.
2
3use std::borrow::Cow;
4use std::ffi::{CStr, CString};
5use std::time::{Duration, Instant};
6
7use crate::private::try_get_api;
8
9/// A string encoding information about the code being profiled for Godot's built-in profiler.
10///
11/// The string should be in the form of `{file}::{line_number}::{tag}`, where `tag` is an
12/// identifier of the code, usually be the name of the method. None of the substrings should
13/// contain `::`.
14///
15/// To create a `Signature` in the correct form, see [`Signature::new()`] or [`profile_sig!`]. To
16/// create a `Signature` from an existing `CStr` or `CString`, see [`Signature::from_raw()`] and
17/// [`Signature::from_raw_owned()`].
18#[derive(Clone, Eq, PartialEq, Hash, Debug)]
19pub struct Signature<'a> {
20 sig: Cow<'a, CStr>,
21}
22
23impl<'a> Signature<'a> {
24 /// Creates a `Signature` from a `CStr` in the specified format. The format is not
25 /// checked.
26 ///
27 /// Adding profiling data using an invalid `Signature` may cause incorrect information to
28 /// show up in the editor.
29 #[inline(always)]
30 pub const fn from_raw(sig: &'a CStr) -> Self {
31 Signature {
32 sig: Cow::Borrowed(sig),
33 }
34 }
35
36 /// Creates a `Signature` from a NUL-terminated byte slice, containing a string in the
37 /// specified format. Neither the format nor whether the slice is correctly NUL-terminated
38 /// is checked.
39 ///
40 /// This is a convenience method for `Signature::from_raw(CStr::from_bytes_with_nul_unchecked(bytes))`.
41 ///
42 /// Adding profiling data using an invalid `Signature` may cause incorrect information to
43 /// show up in the editor.
44 ///
45 /// # Safety
46 ///
47 /// This function will cast the provided `bytes` to a `CStr` wrapper without performing any
48 /// sanity checks. The provided slice **must** be nul-terminated and not contain any
49 /// interior nul bytes.
50 #[inline(always)]
51 pub unsafe fn from_bytes_with_nul_unchecked(bytes: &'a [u8]) -> Self {
52 let sig = CStr::from_bytes_with_nul_unchecked(bytes);
53 Self::from_raw(sig)
54 }
55
56 /// Create a borrowed version of `self` for repeated use with [`add_data()`][Self::add_data()] or [`profile()`][Self::add_data()].
57 #[inline(always)]
58 pub fn borrow(&self) -> Signature<'_> {
59 Signature {
60 sig: Cow::Borrowed(&*self.sig),
61 }
62 }
63
64 /// Add a data point to Godot's built-in profiler using this signature.
65 ///
66 /// See the free function [`profiler::add_data()`][add_data()].
67 #[inline]
68 pub fn add_data(&self, time: Duration) {
69 add_data(self.borrow(), time)
70 }
71
72 /// Times a closure and adds the measured time to Godot's built-in profiler with this
73 /// signature, and then returns it's return value.
74 ///
75 /// See the free function [`profiler::profile()`][profile()].
76 #[inline]
77 pub fn profile<F, R>(&self, f: F) -> R
78 where
79 F: FnOnce() -> R,
80 {
81 profile(self.borrow(), f)
82 }
83
84 fn as_ptr(&self) -> *const libc::c_char {
85 self.sig.as_ptr()
86 }
87}
88
89impl Signature<'static> {
90 /// Creates a `Signature` in the correct form using the given variables. The format is
91 /// checked at runtime.
92 ///
93 /// # Panics
94 ///
95 /// If `file` or `tag` contain `::` or NUL-bytes.
96 #[inline]
97 pub fn new(file: &str, line: u32, tag: &str) -> Self {
98 if file.contains("::") {
99 panic!("file name should not contain `::`");
100 }
101
102 if tag.contains("::") {
103 panic!("tag should not contain `::`");
104 }
105
106 let sig = CString::new(format!("{file}::{line}::{tag}"))
107 .expect("file and tag should not contain NUL bytes");
108 Self::from_raw_owned(sig)
109 }
110
111 /// Creates a `Signature` from an owned `CString` in the specified format. The format is not
112 /// checked.
113 ///
114 /// Adding profiling data using an invalid `Signature` may cause incorrect information to
115 /// show up in the editor.
116 #[inline(always)]
117 pub const fn from_raw_owned(sig: CString) -> Self {
118 Signature {
119 sig: Cow::Owned(sig),
120 }
121 }
122}
123
124/// Add a data point to Godot's built-in profiler. The profiler only has microsecond precision.
125/// Sub-microsecond time is truncated.
126///
127/// If the GDNative API is not initialized at the point when this is called, the function will
128/// fail silently.
129///
130/// # Panics
131///
132/// If the number of microseconds in `time` exceeds the range of `u64`.
133#[inline]
134pub fn add_data(signature: Signature<'_>, time: Duration) {
135 if let Some(api) = try_get_api() {
136 let time_in_usec = u64::try_from(time.as_micros())
137 .expect("microseconds in `time` should not exceed the range of u64");
138
139 unsafe {
140 (api.godot_nativescript_profiling_add_data)(signature.as_ptr(), time_in_usec);
141 }
142 }
143}
144
145/// Times a closure and adds the measured time to Godot's built-in profiler with the given
146/// signature, and then returns it's return value.
147#[inline]
148pub fn profile<F, R>(signature: Signature<'_>, f: F) -> R
149where
150 F: FnOnce() -> R,
151{
152 let start = Instant::now();
153 let ret = f();
154 add_data(signature, Instant::now() - start);
155 ret
156}
157
158/// Convenience macro to create a profiling signature with a given tag.
159///
160/// The expanded code will panic at runtime if the file name or `tag` contains `::` or
161/// any NUL-bytes.
162///
163/// See [`Signature`] for more information.
164///
165/// # Examples
166///
167/// ```rust
168/// # fn main() {
169/// use gdnative::profiler::{profile, profile_sig};
170///
171/// let answer = profile(profile_sig!("foo"), || 42);
172/// assert_eq!(answer, 42);
173/// # }
174/// ```
175#[macro_export]
176macro_rules! _profile_sig {
177 ($tag:expr) => {
178 $crate::profiler::Signature::new(file!(), line!(), $tag)
179 };
180}
181
182// Export macro in this module
183pub use _profile_sig as profile_sig;