Skip to main content

servoshell/
lib.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use cfg_if::cfg_if;
6
7#[cfg(test)]
8mod test;
9
10#[cfg(not(target_os = "android"))]
11mod backtrace;
12#[cfg(not(target_env = "ohos"))]
13mod crash_handler;
14#[cfg(not(any(target_os = "android", target_env = "ohos")))]
15pub(crate) mod desktop;
16#[cfg(any(target_os = "android", target_env = "ohos"))]
17mod egl;
18#[cfg(not(any(target_os = "android", target_env = "ohos")))]
19mod panic_hook;
20mod parser;
21mod prefs;
22#[cfg(not(any(target_os = "android", target_env = "ohos")))]
23mod resources;
24mod running_app_state;
25mod webdriver;
26mod window;
27
28pub mod platform {
29    #[cfg(target_os = "macos")]
30    pub use crate::platform::macos::deinit;
31
32    #[cfg(target_os = "macos")]
33    pub mod macos;
34
35    #[cfg(not(target_os = "macos"))]
36    pub fn deinit(_clean_shutdown: bool) {}
37}
38
39#[cfg(not(any(target_os = "android", target_env = "ohos")))]
40pub fn main() {
41    desktop::cli::main()
42}
43
44pub fn init_crypto() {
45    rustls::crypto::aws_lc_rs::default_provider()
46        .install_default()
47        .expect("Error initializing crypto provider");
48}
49
50pub fn init_tracing(filter_directives: Option<&str>) {
51    #[cfg(not(feature = "tracing"))]
52    {
53        if filter_directives.is_some() {
54            log::debug!("The tracing feature was not selected - ignoring trace filter directives");
55        }
56    }
57    #[cfg(feature = "tracing")]
58    {
59        use tracing_subscriber::layer::SubscriberExt;
60        let subscriber = tracing_subscriber::registry();
61
62        #[cfg(feature = "tracing-perfetto")]
63        let subscriber = {
64            // Set up a PerfettoLayer for performance tracing.
65            // The servo.pftrace file can be uploaded to https://ui.perfetto.dev for analysis.
66            let file = std::fs::File::create("servo.pftrace").unwrap();
67            let perfetto_layer = tracing_perfetto::PerfettoLayer::new(std::sync::Mutex::new(file))
68                .with_filter_by_marker(|field_name| field_name == "servo_profiling")
69                .with_debug_annotations(true);
70            subscriber.with(perfetto_layer)
71        };
72
73        #[cfg(feature = "tracing-hitrace")]
74        let subscriber = {
75            // Set up a HitraceLayer for performance tracing.
76            subscriber.with(HitraceLayer::default())
77        };
78
79        // Filter events and spans by the directives in SERVO_TRACING, using EnvFilter as a global filter.
80        // <https://docs.rs/tracing-subscriber/0.3.18/tracing_subscriber/layer/index.html#global-filtering>
81        let filter_builder = tracing_subscriber::EnvFilter::builder()
82            .with_default_directive(tracing::level_filters::LevelFilter::OFF.into());
83        let filter = if let Some(filters) = &filter_directives {
84            filter_builder.parse_lossy(filters)
85        } else {
86            filter_builder
87                .with_env_var("SERVO_TRACING")
88                .from_env_lossy()
89        };
90
91        let subscriber = subscriber.with(filter);
92
93        // Same as SubscriberInitExt::init, but avoids initialising the tracing-log compat layer,
94        // since it would break Servo’s FromScriptLogger and FromEmbederLogger.
95        // <https://docs.rs/tracing-subscriber/0.3.18/tracing_subscriber/util/trait.SubscriberInitExt.html#method.init>
96        // <https://docs.rs/tracing/0.1.40/tracing/#consuming-log-records>
97        tracing::subscriber::set_global_default(subscriber)
98            .expect("Failed to set tracing subscriber");
99
100        // Capture a first event, including the explicit wallclock time.
101        // The event itself is useful when investigating startup time.
102        // `wallclock_ns` allows us to ground the time, so we can compare
103        // against an external timestamp from before starting servoshell.
104        // In practice, the perfetto timestamp seems to be the same, but
105        // we shouldn't assume this, since different backends may behave differently.
106        servo::profile_traits::info_event!(
107            "servoshell::startup_tracing_initialized",
108            wallclock_ns = std::time::SystemTime::now()
109                .duration_since(std::time::UNIX_EPOCH)
110                .map(|duration| duration.as_nanos() as u64)
111                .unwrap_or(0)
112        );
113    }
114}
115
116pub const VERSION: &str = concat!("Servo ", env!("CARGO_PKG_VERSION"), "-", env!("GIT_SHA"));
117
118/// Plumbs tracing spans into HiTrace, with the following caveats:
119///
120/// - We ignore spans unless they have a `servo_profiling` field.
121/// - We map span entry ([`Layer::on_enter`]) to `OH_HiTrace_StartTraceEx(metadata.name(), fields)`.
122/// - We map span exit ([`Layer::on_exit`]) to `OH_HiTrace_FinishTraceEx()`.
123///
124/// As a result, within each thread, spans must exit in reverse order of their entry, otherwise the
125/// resultant profiling data will be incorrect (see the section below). This is not necessarily the
126/// case for tracing spans, since there can be multiple [trace trees], so we check that this
127/// invariant is upheld when debug assertions are enabled, logging errors if it is violated.
128///
129/// [trace trees]: https://docs.rs/tracing/0.1.40/tracing/span/index.html#span-relationships
130///
131/// # Uniquely identifying spans
132///
133/// We need to ensure that the start and end points of one span are not mixed up with other spans.
134/// For now, we use the HiTrace [synchronous API], which restricts how spans must behave.
135///
136/// In the HiTrace [synchronous API], spans must have stack-like behaviour, because spans are keyed
137/// entirely on their *name* string, and OH_HiTrace_FinishTrace always ends the most recent span.
138/// While synchronous API spans are thread-local, callers could still violate this invariant with
139/// reentrant or asynchronous code.
140///
141/// In the [asynchronous API], spans are keyed on a (*name*,*taskId*) pair, where *name* is again
142/// a string, and *taskId* is an arbitrary [`i32`]. This makes *taskId* a good place for a unique
143/// identifier, but asynchronous spans can cross thread boundaries, so the identifier needs to be
144/// temporally unique in the whole process.
145///
146/// Tracing spans have such an identifier ([`Id`]), but they’re [`u64`]-based, and their format
147/// is an internal implementation detail of the [`Subscriber`]. For [`Registry`], those values
148/// [come from] a [packed representation] of a generation number, thread number, page number, and
149/// variable-length index. This makes them hard to compress robustly into an [`i32`].
150///
151/// If we move to the asynchronous API, we will need to generate our own *taskId* values, perhaps
152/// by combining some sort of thread id with a thread-local atomic counter. [`ThreadId`] is opaque
153/// in stable Rust, and converts to a [`u64`] in unstable Rust, so we would also need to make our
154/// own thread ids, perhaps by having a global atomic counter cached in a thread-local.
155///
156/// [synchronous API]: https://docs.rs/hitrace-sys/0.1.9/hitrace_sys/fn.OH_HiTrace_StartTraceEx.html
157/// [asynchronous API]: https://docs.rs/hitrace-sys/0.1.9/hitrace_sys/fn.OH_HiTrace_StartAsyncTraceEx.html
158/// [`Registry`]: tracing_subscriber::Registry
159/// [come from]: https://docs.rs/tracing-subscriber/0.3.18/src/tracing_subscriber/registry/sharded.rs.html#237-269
160/// [packed representation]: https://docs.rs/sharded-slab/0.1.7/sharded_slab/trait.Config.html
161/// [`ThreadId`]: std::thread::ThreadId
162#[cfg(feature = "tracing-hitrace")]
163#[derive(Default)]
164struct HitraceLayer {}
165
166cfg_if! {
167    if #[cfg(feature = "tracing-hitrace")] {
168        use std::cell::RefCell;
169        use std::fmt;
170        use std::fmt::Write;
171
172        use tracing::field::{Field, Visit};
173        use tracing::Level;
174        use tracing::span::Id;
175        use tracing::Subscriber;
176        use tracing_subscriber::Layer;
177
178        #[derive(Default)]
179        struct HitraceFields(String);
180
181        impl HitraceFields {
182            fn record_value(&mut self, field: &Field, value: &dyn fmt::Debug) {
183                if field.name() == "servo_profiling" {
184                    return;
185                }
186
187                if !self.0.is_empty() {
188                    self.0.push(',');
189                }
190                write!(&mut self.0, "{}={value:?}", field.name())
191                    .expect("Writing to a String should never fail");
192            }
193        }
194
195        impl Visit for HitraceFields {
196            fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
197                self.record_value(field, value);
198            }
199        }
200
201        #[cfg(debug_assertions)]
202        thread_local! {
203            /// Stack of span names, to ensure the HiTrace synchronous API is not misused.
204            static HITRACE_NAME_STACK: RefCell<Vec<String>> = RefCell::default();
205        }
206
207        /// Map a tracing level to a HiTrace level.
208        ///
209        /// The log-level of hitrace itself can only be configured globally,
210        /// which drastically increases the amount of data that needs to be saved.
211        /// For our purposes, the tracing-rs internal filtering is sufficient,
212        /// and we don't need additional `debug` log level on the hitrace side,
213        /// so we just map anything below INFO to INFO.
214        fn convert_level(tracing_level: Level) -> hitrace::api_19::HiTraceOutputLevel {
215            if tracing_level < Level::INFO {
216                Level::INFO.into()
217            } else {
218                tracing_level.into()
219            }
220        }
221
222        impl<S: Subscriber + for<'lookup> tracing_subscriber::registry::LookupSpan<'lookup>>
223            Layer<S> for HitraceLayer
224        {
225            fn on_new_span(
226                &self,
227                attrs: &tracing::span::Attributes<'_>,
228                id: &Id,
229                ctx: tracing_subscriber::layer::Context<'_, S>,
230            ) {
231                if attrs.metadata().fields().field("servo_profiling").is_none() {
232                    return;
233                }
234
235                let Some(span) = ctx.span(id) else {
236                    return;
237                };
238
239                let mut fields = HitraceFields::default();
240                attrs.record(&mut fields);
241                span.extensions_mut().insert(fields);
242            }
243
244            fn on_record(
245                &self,
246                id: &Id,
247                values: &tracing::span::Record<'_>,
248                ctx: tracing_subscriber::layer::Context<'_, S>,
249            ) {
250                let Some(span) = ctx.span(id) else {
251                    return;
252                };
253
254                let mut extensions = span.extensions_mut();
255                let Some(fields) = extensions.get_mut::<HitraceFields>() else {
256                    return;
257                };
258
259                values.record(fields);
260            }
261
262            fn on_enter(&self, id: &Id, ctx: tracing_subscriber::layer::Context<'_, S>) {
263                let Some(span) = ctx.span(id) else {
264                    return;
265                };
266                let extensions = span.extensions();
267                // The HitraceFields extension being present implies `servo_profiling` was set.
268                let Some(fields) = extensions.get::<HitraceFields>() else {
269                    return;
270                };
271                // We can't take the String out of the extensions, so
272                //  unfortunately, this won't reuse the allocation.
273                let custom_args = std::ffi::CString::new(fields.0.as_str())
274                        .expect("Failed to convert to CString");
275
276                let metadata = span.metadata();
277                let level = convert_level(*metadata.level());
278                let name = metadata.name();
279
280                #[cfg(debug_assertions)]
281                HITRACE_NAME_STACK.with_borrow_mut(|stack|
282                    stack.push(name.to_owned()));
283
284                hitrace::start_trace_ex(
285                    level,
286                    &std::ffi::CString::new(name)
287                        .expect("Failed to convert str to CString"),
288                    &custom_args,
289                );
290
291            }
292
293            fn on_event(&self, event: &tracing::Event<'_>, _ctx: tracing_subscriber::layer::Context<'_, S>) {
294                let mut fields = HitraceFields::default();
295                event.record(&mut fields);
296                let metadata = event.metadata();
297                let level = convert_level(*metadata.level());
298
299                hitrace::start_trace_ex(
300                    level,
301                    &std::ffi::CString::new(metadata.name())
302                        .expect("Failed to convert str to CString"),
303                    &std::ffi::CString::new(fields.0)
304                        .expect("Failed to convert str to CString"),
305                );
306
307                hitrace::finish_trace_ex(level);
308            }
309
310
311            fn on_exit(&self, id: &Id, ctx: tracing_subscriber::layer::Context<'_, S>) {
312                let Some(span) = ctx.span(id) else {
313                    return;
314                };
315                if span.extensions().get::<HitraceFields>().is_none() {
316                    return;
317                }
318
319                let level = convert_level(*span.metadata().level());
320                hitrace::finish_trace_ex(level);
321
322                #[cfg(debug_assertions)]
323                HITRACE_NAME_STACK.with_borrow_mut(|stack| {
324                    let metadata = span.metadata();
325                    if stack.last().map(|name| &**name) != Some(metadata.name()) {
326                        log::error!(
327                            "Tracing span out of order: {} (stack: {:?})",
328                            metadata.name(),
329                            stack
330                        );
331                    }
332                    if !stack.is_empty() {
333                        stack.pop();
334                    }
335                });
336            }
337
338        }
339    }
340}