bp3d_debug/profiler/
section.rs

1// Copyright (c) 2025, BlockProject 3D
2//
3// All rights reserved.
4//
5// Redistribution and use in source and binary forms, with or without modification,
6// are permitted provided that the following conditions are met:
7//
8//     * Redistributions of source code must retain the above copyright notice,
9//       this list of conditions and the following disclaimer.
10//     * Redistributions in binary form must reproduce the above copyright notice,
11//       this list of conditions and the following disclaimer in the documentation
12//       and/or other materials provided with the distribution.
13//     * Neither the name of BlockProject 3D nor the names of its contributors
14//       may be used to endorse or promote products derived from this software
15//       without specific prior written permission.
16//
17// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
21// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29use crate::field::FieldSet;
30use crate::util::Location;
31use std::num::NonZeroU32;
32use std::sync::OnceLock;
33use super::instant::Instant;
34
35#[repr(u8)]
36#[derive(Debug, Copy, Clone, Eq, PartialEq)]
37pub enum Level {
38    /// A section located in a critically hot path.
39    Critical = 0,
40
41    /// A periodic section.
42    Periodic = 1,
43
44    // An event based section.
45    Event = 2,
46}
47
48thread_local! {
49    static CUR_TIME: Instant = Instant::now();
50}
51
52pub struct Entered<'a, const N: usize> {
53    id: NonZeroU32,
54    start: u64,
55    fields: FieldSet<'a, N>,
56}
57
58impl<const N: usize> Drop for Entered<'_, N> {
59    fn drop(&mut self) {
60        let end = CUR_TIME.with(|v| v.elapsed().as_nanos() as _);
61        crate::engine::get().section_record(self.id, self.start, end, self.fields.as_ref());
62    }
63}
64
65pub struct Section {
66    name: &'static str,
67    location: Location,
68    level: Level,
69    parent: Option<&'static Section>,
70    id: OnceLock<NonZeroU32>,
71}
72
73impl Section {
74    pub const fn new(name: &'static str, location: Location, level: Level) -> Self {
75        Self {
76            name,
77            location,
78            level,
79            parent: None,
80            id: OnceLock::new(),
81        }
82    }
83
84    pub const fn set_parent(mut self, parent: &'static Section) -> Self {
85        self.parent = Some(parent);
86        self
87    }
88
89    pub fn name(&self) -> &'static str {
90        self.name
91    }
92
93    pub fn location(&self) -> &Location {
94        &self.location
95    }
96
97    pub fn level(&self) -> Level {
98        self.level
99    }
100
101    pub fn parent(&self) -> Option<&'static Section> {
102        self.parent
103    }
104
105    pub fn get_id(&'static self) -> &'static NonZeroU32 {
106        self.id
107            .get_or_init(|| crate::engine::get().section_register(self))
108    }
109
110    pub fn enter<'a, const N: usize>(&'static self, fields: FieldSet<'a, N>) -> Entered<'a, N> {
111        let id = self.get_id();
112        Entered {
113            id: *id,
114            start: CUR_TIME.with(|v| v.elapsed().as_nanos() as _),
115            fields,
116        }
117    }
118}
119
120#[cfg(test)]
121mod tests {
122    use crate::field::FieldSet;
123    use crate::profiler::section::{Level, Section};
124    use crate::{fields, location, profiler_section_start};
125
126    #[test]
127    fn basic() {
128        static _SECTION: Section = Section::new("api_test", location!(), Level::Event);
129    }
130
131    #[test]
132    fn api_test() {
133        static SECTION: Section = Section::new("api_test", location!(), Level::Event);
134        static _SECTION2: Section =
135            Section::new("api_test2", location!(), Level::Event).set_parent(&SECTION);
136        SECTION.enter(FieldSet::new(fields!()));
137        SECTION.enter(FieldSet::new(fields!({ test = 42 })));
138        SECTION.enter(FieldSet::new(fields!({ test = "test 123" })));
139        SECTION.enter(FieldSet::new(fields!({ test = 42.42 })));
140        SECTION.enter(FieldSet::new(fields!({test=?Level::Event})));
141        SECTION.enter(FieldSet::new(fields!({test=?Level::Event} {test2=42})));
142        let value = 32;
143        let str = "this is a test";
144        let lvl = Level::Event;
145        SECTION.enter(FieldSet::new(fields!({value} {str} {?lvl} {test = value})));
146    }
147
148    #[test]
149    fn api_test2() {
150        let value = 32;
151        let str = "this is a test";
152        let lvl = Level::Event;
153        profiler_section_start!(API_TEST, Level::Event);
154        profiler_section_start!(API2_TEST: API_TEST, Level::Event);
155        profiler_section_start!(API3_TEST_WITH_PARAMS: API2_TEST, Level::Event, {value} {str} {?lvl} {test=value});
156    }
157}