Skip to main content

perfetto_sdk/
lib.rs

1// Copyright (C) 2025 Rivos Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#![doc = include_str!("../README.md")]
16#![deny(missing_docs)]
17#![warn(clippy::undocumented_unsafe_blocks)]
18#![cfg_attr(
19    feature = "intrinsics",
20    allow(internal_features),
21    feature(core_intrinsics)
22)]
23
24/// Data source module.
25pub mod data_source;
26
27/// Heap buffer module.
28pub mod heap_buffer;
29
30/// Protobuf decoder module.
31pub mod pb_decoder;
32
33/// Protobuf message module.
34pub mod pb_msg;
35
36/// Protobuf utils module.
37pub mod pb_utils;
38
39/// Producer module.
40pub mod producer;
41
42/// Protobuf bindings module.
43pub mod protos;
44
45/// Stream writer module.
46pub mod stream_writer;
47
48/// Tracing session module.
49pub mod tracing_session;
50
51/// Track event module.
52pub mod track_event;
53
54// FNV-1a 64-bit constants
55const FNV64_OFFSET: u64 = 0xcbf29ce484222325;
56const FNV64_PRIME: u64 = 0x00000100000001B3;
57
58/// Computes the FNV-1a hash of `bytes`.
59pub const fn fnv1a(bytes: &[u8]) -> u64 {
60    let mut hash = FNV64_OFFSET;
61    let mut i = 0;
62    while i < bytes.len() {
63        hash ^= bytes[i] as u64;
64        hash = hash.wrapping_mul(FNV64_PRIME);
65        i += 1;
66    }
67    hash
68}
69
70/// Helper macro that use `likely` intrinsic branch prediction hint.
71#[cfg(feature = "intrinsics")]
72#[doc(hidden)]
73#[macro_export]
74macro_rules! __likely {
75    ($e:expr) => {{ std::intrinsics::likely($e) }};
76}
77
78/// Helper macro that ignores branch prediction hint.
79#[cfg(not(feature = "intrinsics"))]
80#[doc(hidden)]
81#[macro_export]
82macro_rules! __likely {
83    ($e:expr) => {{ $e }};
84}
85
86/// Helper macro that use `unlikely` intrinsic branch prediction hint.
87#[cfg(feature = "intrinsics")]
88#[doc(hidden)]
89#[macro_export]
90macro_rules! __unlikely {
91    ($e:expr) => {{ std::intrinsics::unlikely($e) }};
92}
93
94/// Helper macro that ignores branch prediction hint.
95#[cfg(not(feature = "intrinsics"))]
96#[doc(hidden)]
97#[macro_export]
98macro_rules! __unlikely {
99    ($e:expr) => {{ $e }};
100}
101
102/// Internal utility function that converts `Box<T>` to `*mut T`.
103#[doc(hidden)]
104pub fn __box_as_mut_ptr<T: ?Sized>(b: &mut Box<T>) -> *mut T {
105    // TODO(reveman): Use Box::as_mut_ptr() instead when stable.
106    &raw mut **b
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112    use crate::producer::{Backends, Producer, ProducerInitArgsBuilder};
113    use crate::tracing_session::*;
114    use std::sync::{Mutex, MutexGuard, Once};
115
116    static INIT_TEST_ENVIRONMENT: Once = Once::new();
117    static TEST_ENVIRONMENT_MUTEX: Mutex<()> = Mutex::new(());
118
119    pub(crate) const PRODUCER_SHMEM_SIZE_HINT_KB: u32 = 64;
120
121    // Perfetto uses global state internally that cannot be uninitialized so the
122    // test environment and registered data sources must also be global.
123    pub(crate) fn acquire_test_environment() -> MutexGuard<'static, ()> {
124        INIT_TEST_ENVIRONMENT.call_once(|| {
125            let producer_args = ProducerInitArgsBuilder::new()
126                .backends(Backends::IN_PROCESS)
127                .shmem_size_hint_kb(PRODUCER_SHMEM_SIZE_HINT_KB);
128            Producer::init(producer_args.build());
129        });
130        TEST_ENVIRONMENT_MUTEX.lock().unwrap()
131    }
132
133    #[derive(Default)]
134    #[must_use = "This is a builder; remember to call `.build()` (or keep chaining)."]
135    pub(crate) struct TracingSessionBuilder {
136        data_source_name: String,
137        enabled_categories: Vec<String>,
138        disabled_categories: Vec<String>,
139    }
140
141    impl TracingSessionBuilder {
142        pub fn new() -> Self {
143            Self::default()
144        }
145
146        #[must_use = "Builder methods return an updated builder; use the returned value or keep chaining."]
147        pub fn set_data_source_name(mut self, name: impl Into<String>) -> Self {
148            self.data_source_name = name.into();
149            self
150        }
151
152        #[must_use = "Builder methods return an updated builder; use the returned value or keep chaining."]
153        pub fn add_enabled_category(mut self, category: impl Into<String>) -> Self {
154            self.enabled_categories.push(category.into());
155            self
156        }
157
158        #[must_use = "Builder methods return an updated builder; use the returned value or keep chaining."]
159        pub fn add_disabled_category(mut self, category: impl Into<String>) -> Self {
160            self.disabled_categories.push(category.into());
161            self
162        }
163
164        fn build_proto_config(&self) -> Vec<u8> {
165            use crate::{
166                heap_buffer::HeapBuffer,
167                pb_msg::{PbMsg, PbMsgWriter},
168                protos::config::{
169                    data_source_config::DataSourceConfig,
170                    trace_config::{TraceConfig, TraceConfigBufferConfig, TraceConfigDataSource},
171                    track_event::track_event_config::TrackEventConfig,
172                },
173            };
174            let writer = PbMsgWriter::new();
175            let hb = HeapBuffer::new(&writer.writer);
176            let mut msg = PbMsg::new(&writer).unwrap();
177            {
178                let mut cfg = TraceConfig { msg: &mut msg };
179                cfg.set_buffers(|buf_cfg: &mut TraceConfigBufferConfig| {
180                    buf_cfg.set_size_kb(1024);
181                });
182                cfg.set_data_sources(|data_sources: &mut TraceConfigDataSource| {
183                    data_sources.set_config(|ds_cfg: &mut DataSourceConfig| {
184                        ds_cfg.set_name(&self.data_source_name);
185                        if !self.enabled_categories.is_empty()
186                            || !self.disabled_categories.is_empty()
187                        {
188                            ds_cfg.set_track_event_config(|te_cfg: &mut TrackEventConfig| {
189                                for enabled_catagory in &self.enabled_categories {
190                                    te_cfg.set_enabled_categories(enabled_catagory);
191                                }
192                                for disabled_catagory in &self.disabled_categories {
193                                    te_cfg.set_disabled_categories(disabled_catagory);
194                                }
195                            });
196                        }
197                    });
198                });
199            }
200            msg.finalize();
201            let cfg_size = writer.writer.get_written_size();
202            let mut cfg_buffer: Vec<u8> = vec![0u8; cfg_size];
203            hb.copy_into(&mut cfg_buffer);
204
205            cfg_buffer
206        }
207
208        pub fn build(&self) -> Result<TracingSession, TracingSessionError> {
209            let config = self.build_proto_config();
210            let mut ts = TracingSession::in_process()?;
211            ts.setup(&config);
212            Ok(ts)
213        }
214    }
215
216    #[test]
217    fn fnv1a_hash() {
218        assert_eq!(fnv1a("mytrack".as_bytes()), 9332035348890697650);
219    }
220
221    #[test]
222    fn unlikely_conditional() {
223        if __unlikely!(fnv1a("mystring".as_bytes()) == 0) {
224            unreachable!();
225        }
226    }
227}