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}