diff options
Diffstat (limited to 'tools/profiler/rust-api/src')
-rw-r--r-- | tools/profiler/rust-api/src/gecko_bindings/glue.rs | 53 | ||||
-rw-r--r-- | tools/profiler/rust-api/src/gecko_bindings/mod.rs | 21 | ||||
-rw-r--r-- | tools/profiler/rust-api/src/gecko_bindings/profiling_categories.rs | 32 | ||||
-rw-r--r-- | tools/profiler/rust-api/src/json_writer.rs | 100 | ||||
-rw-r--r-- | tools/profiler/rust-api/src/label.rs | 137 | ||||
-rw-r--r-- | tools/profiler/rust-api/src/lib.rs | 30 | ||||
-rw-r--r-- | tools/profiler/rust-api/src/marker/deserializer_tags_state.rs | 116 | ||||
-rw-r--r-- | tools/profiler/rust-api/src/marker/mod.rs | 283 | ||||
-rw-r--r-- | tools/profiler/rust-api/src/marker/options.rs | 138 | ||||
-rw-r--r-- | tools/profiler/rust-api/src/marker/schema.rs | 233 | ||||
-rw-r--r-- | tools/profiler/rust-api/src/profiler_state.rs | 81 | ||||
-rw-r--r-- | tools/profiler/rust-api/src/thread.rs | 23 | ||||
-rw-r--r-- | tools/profiler/rust-api/src/time.rs | 71 |
13 files changed, 1318 insertions, 0 deletions
diff --git a/tools/profiler/rust-api/src/gecko_bindings/glue.rs b/tools/profiler/rust-api/src/gecko_bindings/glue.rs new file mode 100644 index 0000000000..531f727a00 --- /dev/null +++ b/tools/profiler/rust-api/src/gecko_bindings/glue.rs @@ -0,0 +1,53 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::gecko_bindings::{bindings, structs::mozilla}; +use crate::json_writer::JSONWriter; +use crate::marker::deserializer_tags_state::{ + get_marker_type_functions_read_guard, MarkerTypeFunctions, +}; +use std::ops::DerefMut; +use std::os::raw::{c_char, c_void}; + +#[no_mangle] +pub unsafe extern "C" fn gecko_profiler_serialize_marker_for_tag( + deserializer_tag: u8, + payload: *const u8, + payload_size: usize, + json_writer: &mut mozilla::baseprofiler::SpliceableJSONWriter, +) { + let marker_type_functions = get_marker_type_functions_read_guard(); + let &MarkerTypeFunctions { + transmute_and_stream_fn, + marker_type_name_fn, + .. + } = marker_type_functions.get(deserializer_tag); + let mut json_writer = JSONWriter::new(&mut *json_writer); + + // Serialize the marker type name first. + json_writer.string_property("type", marker_type_name_fn()); + // Serialize the marker payload now. + transmute_and_stream_fn(payload, payload_size, &mut json_writer); +} + +#[no_mangle] +pub unsafe extern "C" fn gecko_profiler_stream_marker_schemas( + json_writer: &mut mozilla::baseprofiler::SpliceableJSONWriter, + streamed_names_set: *mut c_void, +) { + let marker_type_functions = get_marker_type_functions_read_guard(); + + for funcs in marker_type_functions.iter() { + let marker_name = (funcs.marker_type_name_fn)(); + let mut marker_schema = (funcs.marker_type_display_fn)(); + + bindings::gecko_profiler_marker_schema_stream( + json_writer, + marker_name.as_ptr() as *const c_char, + marker_name.len(), + marker_schema.pin.deref_mut().as_mut_ptr(), + streamed_names_set, + ) + } +} diff --git a/tools/profiler/rust-api/src/gecko_bindings/mod.rs b/tools/profiler/rust-api/src/gecko_bindings/mod.rs new file mode 100644 index 0000000000..f1ec667bb2 --- /dev/null +++ b/tools/profiler/rust-api/src/gecko_bindings/mod.rs @@ -0,0 +1,21 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +//! Gecko's C++ bindings for the profiler. + +#[allow( + dead_code, + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + missing_docs +)] +pub mod structs { + include!(concat!(env!("OUT_DIR"), "/gecko/bindings.rs")); +} + +pub use self::structs as bindings; + +mod glue; +pub mod profiling_categories; diff --git a/tools/profiler/rust-api/src/gecko_bindings/profiling_categories.rs b/tools/profiler/rust-api/src/gecko_bindings/profiling_categories.rs new file mode 100644 index 0000000000..0f24aa9c35 --- /dev/null +++ b/tools/profiler/rust-api/src/gecko_bindings/profiling_categories.rs @@ -0,0 +1,32 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! This file contains the generated ProfilingCategory and ProfilingCategoryPair enums. +//! +//! The contents of this module are generated by +//! `mozglue/baseprofiler/generate_profiling_categories.py`, from +//! 'mozglue/baseprofiler/core/profiling_categories.yaml`. + +include!(mozbuild::objdir_path!( + "tools/profiler/rust-api/src/gecko_bindings/profiling_categories.rs" +)); + +/// Helper macro that returns the profiling category pair from either only +/// "category", or "category + sub category" pair. Refer to `profiling_categories.yaml` +/// or generated `profiling_categories.rs` to see all the marker categories. +/// This is useful to make the APIs similar to each other since +/// `gecko_profiler_label!` API also requires the same syntax. +/// +/// Example usages: +/// - `gecko_profiler_category!(DOM)` +/// - `gecko_profiler_category!(JavaScript, Parsing)` +#[macro_export] +macro_rules! gecko_profiler_category { + ($category:ident) => { + $crate::ProfilingCategoryPair::$category(None) + }; + ($category:ident, $subcategory:ident) => { + $crate::ProfilingCategoryPair::$category(Some($crate::$category::$subcategory)) + }; +} diff --git a/tools/profiler/rust-api/src/json_writer.rs b/tools/profiler/rust-api/src/json_writer.rs new file mode 100644 index 0000000000..66c12dda04 --- /dev/null +++ b/tools/profiler/rust-api/src/json_writer.rs @@ -0,0 +1,100 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! Gecko JSON writer support for marker API. + +use crate::gecko_bindings::{bindings, structs::mozilla}; +use std::os::raw::c_char; + +/// Wrapper for the C++ SpliceableJSONWriter object. It exposes some methods to +/// add various properties to the JSON. +#[derive(Debug)] +pub struct JSONWriter<'a>(&'a mut mozilla::baseprofiler::SpliceableJSONWriter); + +impl<'a> JSONWriter<'a> { + /// Constructor for the JSONWriter object. It takes a C++ SpliceableJSONWriter + /// reference as its argument and stores it for later accesses. + pub(crate) fn new(json_writer: &'a mut mozilla::baseprofiler::SpliceableJSONWriter) -> Self { + JSONWriter(json_writer) + } + + /// Adds an int property to the JSON. + /// Prints: "<name>": <value> + pub fn int_property(&mut self, name: &str, value: i64) { + unsafe { + bindings::gecko_profiler_json_writer_int_property( + self.0, + name.as_ptr() as *const c_char, + name.len(), + value, + ); + } + } + + /// Adds a float property to the JSON. + /// Prints: "<name>": <value> + pub fn float_property(&mut self, name: &str, value: f64) { + unsafe { + bindings::gecko_profiler_json_writer_float_property( + self.0, + name.as_ptr() as *const c_char, + name.len(), + value, + ); + } + } + + /// Adds an bool property to the JSON. + /// Prints: "<name>": <value> + pub fn bool_property(&mut self, name: &str, value: bool) { + unsafe { + bindings::gecko_profiler_json_writer_bool_property( + self.0, + name.as_ptr() as *const c_char, + name.len(), + value, + ); + } + } + + /// Adds a string property to the JSON. + /// Prints: "<name>": "<value>" + pub fn string_property(&mut self, name: &str, value: &str) { + unsafe { + bindings::gecko_profiler_json_writer_string_property( + self.0, + name.as_ptr() as *const c_char, + name.len(), + value.as_ptr() as *const c_char, + value.len(), + ); + } + } + + /// Adds a unique string property to the JSON. + /// Prints: "<name>": <string_table_index> + pub fn unique_string_property(&mut self, name: &str, value: &str) { + unsafe { + bindings::gecko_profiler_json_writer_unique_string_property( + self.0, + name.as_ptr() as *const c_char, + name.len(), + value.as_ptr() as *const c_char, + value.len(), + ); + } + } + + /// Adds a null property to the JSON. + /// Prints: "<name>": null + pub fn null_property(&mut self, name: &str) { + unsafe { + bindings::gecko_profiler_json_writer_null_property( + self.0, + name.as_ptr() as *const c_char, + name.len(), + ); + } + } +} diff --git a/tools/profiler/rust-api/src/label.rs b/tools/profiler/rust-api/src/label.rs new file mode 100644 index 0000000000..10970c90ad --- /dev/null +++ b/tools/profiler/rust-api/src/label.rs @@ -0,0 +1,137 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! Gecko profiler label support. +//! +//! Use the `profiler_label!` macro directly instead of using `AutoProfilerLabel`. +//! See the `profiler_label!` macro documentation on how to use it. + +#[cfg(feature = "enabled")] +use crate::gecko_bindings::{ + bindings, profiling_categories::ProfilingCategoryPair, structs::mozilla, +}; + +/// RAII object that constructs and destroys a C++ AutoProfilerLabel object +/// pointed to be the specified reference. +/// Use `profiler_label!` macro directly instead of this, if possible. +#[cfg(feature = "enabled")] +pub struct AutoProfilerLabel<'a>(&'a mut mozilla::AutoProfilerLabel); + +#[cfg(feature = "enabled")] +impl<'a> AutoProfilerLabel<'a> { + /// Creates a new AutoProfilerLabel with the specified label type. + /// + /// unsafe since the caller must ensure that `label` is allocated on the + /// stack. + #[inline] + pub unsafe fn new( + label: &mut std::mem::MaybeUninit<mozilla::AutoProfilerLabel>, + category_pair: ProfilingCategoryPair, + ) -> AutoProfilerLabel { + bindings::gecko_profiler_construct_label( + label.as_mut_ptr(), + category_pair.to_cpp_enum_value(), + ); + AutoProfilerLabel(&mut *label.as_mut_ptr()) + } +} + +#[cfg(feature = "enabled")] +impl<'a> Drop for AutoProfilerLabel<'a> { + #[inline] + fn drop(&mut self) { + unsafe { + bindings::gecko_profiler_destruct_label(self.0); + } + } +} + +/// Place a Gecko profiler label on the stack. +/// +/// The first `category` argument must be the name of a variant of `ProfilerLabelCategoryPair` +/// and the second optional `subcategory` argument must be one of the sub variants of +/// `ProfilerLabelCategoryPair`. All options can be seen either in the +/// profiling_categories.yaml file or generated profiling_categories.rs file. +/// +/// Example usage: +/// ```rust +/// gecko_profiler_label!(Layout); +/// gecko_profiler_label!(JavaScript, Parsing); +/// ``` +/// You can wrap this macro with a block to only label a specific part of a function. +#[cfg(feature = "enabled")] +#[macro_export] +macro_rules! gecko_profiler_label { + ($category:ident) => { + gecko_profiler_label!($crate::ProfilingCategoryPair::$category(None)) + }; + ($category:ident, $subcategory:ident) => { + gecko_profiler_label!($crate::ProfilingCategoryPair::$category(Some( + $crate::$category::$subcategory + ))) + }; + + ($category_path:expr) => { + let mut _profiler_label = ::std::mem::MaybeUninit::< + $crate::gecko_bindings::structs::mozilla::AutoProfilerLabel, + >::uninit(); + let _profiler_label = if $crate::is_active() { + unsafe { + Some($crate::AutoProfilerLabel::new( + &mut _profiler_label, + $category_path, + )) + } + } else { + None + }; + }; +} + +/// No-op when MOZ_GECKO_PROFILER is not defined. +#[cfg(not(feature = "enabled"))] +#[macro_export] +macro_rules! gecko_profiler_label { + ($category:ident) => {}; + ($category:ident, $subcategory:ident) => {}; +} + +#[cfg(test)] +mod tests { + use profiler_macros::gecko_profiler_fn_label; + + #[test] + fn test_gecko_profiler_label() { + gecko_profiler_label!(Layout); + gecko_profiler_label!(JavaScript, Parsing); + } + + #[gecko_profiler_fn_label(DOM)] + fn foo(bar: u32) -> u32 { + bar + } + + #[gecko_profiler_fn_label(Javascript, IonMonkey)] + pub(self) fn bar(baz: i8) -> i8 { + baz + } + + struct A; + + impl A { + #[gecko_profiler_fn_label(Idle)] + pub fn test(&self) -> i8 { + 1 + } + } + + #[test] + fn test_gecko_profiler_fn_label() { + let _: u32 = foo(100000); + let _: i8 = bar(127); + + let a = A; + let _ = a.test(100); + } +} diff --git a/tools/profiler/rust-api/src/lib.rs b/tools/profiler/rust-api/src/lib.rs new file mode 100644 index 0000000000..f92e2937e2 --- /dev/null +++ b/tools/profiler/rust-api/src/lib.rs @@ -0,0 +1,30 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +///! Profiler Rust API + +#[macro_use] +extern crate lazy_static; + +pub mod gecko_bindings; +mod json_writer; +mod label; +mod marker; +mod profiler_state; +mod thread; +mod time; + +pub use gecko_bindings::profiling_categories::*; +pub use json_writer::*; +#[cfg(feature = "enabled")] +pub use label::*; +pub use marker::options::*; +pub use marker::schema::MarkerSchema; +pub use marker::*; +pub use profiler_macros::gecko_profiler_fn_label; +pub use profiler_state::*; +pub use thread::*; +pub use time::*; + +pub use serde::{Deserialize, Serialize}; diff --git a/tools/profiler/rust-api/src/marker/deserializer_tags_state.rs b/tools/profiler/rust-api/src/marker/deserializer_tags_state.rs new file mode 100644 index 0000000000..890cc3f263 --- /dev/null +++ b/tools/profiler/rust-api/src/marker/deserializer_tags_state.rs @@ -0,0 +1,116 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use crate::json_writer::JSONWriter; +use crate::marker::schema::MarkerSchema; +use crate::marker::{transmute_and_stream, ProfilerMarker}; +use std::collections::HashMap; +use std::sync::{RwLock, RwLockReadGuard}; + +lazy_static! { + static ref DESERIALIZER_TAGS_STATE: RwLock<DeserializerTagsState> = + RwLock::new(DeserializerTagsState::new()); +} + +/// A state that keeps track of each marker types and their deserializer tags. +/// They are added during the marker insertion and read during the marker serialization. +pub struct DeserializerTagsState { + /// C++ side accepts only u8 values, but we only know usize values as the + /// unique marker type values. So, we need to keep track of each + /// "marker tag -> deserializer tag" conversions to directly get the + /// deserializer tags of the already added marker types. + pub marker_tag_to_deserializer_tag: HashMap<usize, u8>, + /// Vector of marker type functions. + /// 1-based, i.e.: [0] -> tag 1. Elements are pushed to the end of the vector + /// whenever a new marker type is used in a Firefox session; the content is + /// kept between profiler runs in that session. On the C++ side, we have the + /// same algorithm (althought it's a sized array). See `sMarkerTypeFunctions1Based`. + pub marker_type_functions_1_based: Vec<MarkerTypeFunctions>, +} + +/// Functions that will be stored per marker type, so we can serialize the marker +/// schema and stream the marker payload for a specific type. +pub struct MarkerTypeFunctions { + /// A function that returns the name of the marker type. + pub marker_type_name_fn: fn() -> &'static str, + /// A function that returns a `MarkerSchema`, which contains all the + /// information needed to stream the display schema associated with a + /// marker type. + pub marker_type_display_fn: fn() -> MarkerSchema, + /// A function that can read a serialized payload from bytes and streams it + /// as JSON object properties. + pub transmute_and_stream_fn: + unsafe fn(payload: *const u8, payload_size: usize, json_writer: &mut JSONWriter), +} + +impl DeserializerTagsState { + fn new() -> Self { + DeserializerTagsState { + marker_tag_to_deserializer_tag: HashMap::new(), + marker_type_functions_1_based: vec![], + } + } +} + +/// Get or insert the deserializer tag for each marker type. The tag storage +/// is limited to 255 marker types. This is the same with the C++ side. It's +/// unlikely to reach to this limit, but if that's the case, C++ side needs +/// to change the uint8_t type for the deserializer tag as well. +pub fn get_or_insert_deserializer_tag<T>() -> u8 +where + T: ProfilerMarker, +{ + let unique_marker_tag = &T::marker_type_name as *const _ as usize; + let mut state = DESERIALIZER_TAGS_STATE.write().unwrap(); + + match state.marker_tag_to_deserializer_tag.get(&unique_marker_tag) { + None => { + // It's impossible to have length more than u8. + let deserializer_tag = state.marker_type_functions_1_based.len() as u8 + 1; + debug_assert!( + deserializer_tag < 250, + "Too many rust marker payload types! Please consider increasing the profiler \ + buffer tag size." + ); + + state + .marker_tag_to_deserializer_tag + .insert(unique_marker_tag, deserializer_tag); + state + .marker_type_functions_1_based + .push(MarkerTypeFunctions { + marker_type_name_fn: T::marker_type_name, + marker_type_display_fn: T::marker_type_display, + transmute_and_stream_fn: transmute_and_stream::<T>, + }); + deserializer_tag + } + Some(deserializer_tag) => *deserializer_tag, + } +} + +/// A guard that will be used by the marker FFI functions for getting marker type functions. +pub struct MarkerTypeFunctionsReadGuard { + guard: RwLockReadGuard<'static, DeserializerTagsState>, +} + +impl MarkerTypeFunctionsReadGuard { + pub fn iter<'a>(&'a self) -> impl Iterator<Item = &'a MarkerTypeFunctions> { + self.guard.marker_type_functions_1_based.iter() + } + + pub fn get<'a>(&'a self, deserializer_tag: u8) -> &'a MarkerTypeFunctions { + self.guard + .marker_type_functions_1_based + .get(deserializer_tag as usize - 1) + .expect("Failed to find the marker type functions for given deserializer tag") + } +} + +/// Locks the DESERIALIZER_TAGS_STATE and returns the marker type functions read guard. +pub fn get_marker_type_functions_read_guard() -> MarkerTypeFunctionsReadGuard { + MarkerTypeFunctionsReadGuard { + guard: DESERIALIZER_TAGS_STATE.read().unwrap(), + } +} diff --git a/tools/profiler/rust-api/src/marker/mod.rs b/tools/profiler/rust-api/src/marker/mod.rs new file mode 100644 index 0000000000..1981c0a322 --- /dev/null +++ b/tools/profiler/rust-api/src/marker/mod.rs @@ -0,0 +1,283 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! ## Gecko profiler marker support +//! +//! This marker API has a few different functions that you can use to mark a part of your code. +//! There are three main marker functions to use from Rust: [`add_untyped_marker`], +//! [`add_text_marker`] and [`add_marker`]. They are similar to what we have on +//! the C++ side. Please take a look at the marker documentation in the Firefox +//! source docs to learn more about them: +//! https://firefox-source-docs.mozilla.org/tools/profiler/markers-guide.html +//! +//! ### Simple marker without any additional data +//! +//! The simplest way to add a marker without any additional information is the +//! [`add_untyped_marker`] API. You can use it to mark a part of the code with +//! only a name. E.g.: +//! +//! ``` +//! gecko_profiler::add_untyped_marker( +//! // Name of the marker as a string. +//! "Marker Name", +//! // Category with an optional sub-category. +//! gecko_profiler_category!(Graphics, DisplayListBuilding), +//! // MarkerOptions that keeps options like marker timing and marker stack. +//! Default::default(), +//! ); +//! ``` +//! +//! Please see the [`gecko_profiler_category!`], [`MarkerOptions`],[`MarkerTiming`] +//! and [`MarkerStack`] to learn more about these. +//! +//! You can also give explicit [`MarkerOptions`] value like these: +//! +//! ``` +//! // With both timing and stack fields: +//! MarkerOptions { timing: MarkerTiming::instant_now(), stack: MarkerStack::Full } +//! // Or with some fields as default: +//! MarkerOptions { timing: MarkerTiming::instant_now(), ..Default::default() } +//! ``` +//! +//! ### Marker with only an additional text for more information: +//! +//! The next and slightly more advanced API is [`add_text_marker`]. +//! This is used to add a marker name + a string value for extra information. +//! E.g.: +//! +//! ``` +//! let info = "info about this marker"; +//! ... +//! gecko_profiler::add_text_marker( +//! // Name of the marker as a string. +//! "Marker Name", +//! // Category with an optional sub-category. +//! gecko_profiler_category!(DOM), +//! // MarkerOptions that keeps options like marker timing and marker stack. +//! MarkerOptions { +//! timing: MarkerTiming::instant_now(), +//! ..Default::default() +//! }, +//! // Additional information as a string. +//! info, +//! ); +//! ``` +//! +//! ### Marker with a more complex payload and different visualization in the profiler front-end. +//! +//! [`add_marker`] is the most advanced API that you can use to add different types +//! of values as data to your marker and customize the visualization of that marker +//! in the profiler front-end (profiler.firefox.com). +//! +//! To be able to add a a marker, first you need to create your marker payload +//! struct in your codebase and implement the [`ProfilerMarker`] trait like this: +//! +//! ``` +//! #[derive(Serialize, Deserialize, Debug)] +//! pub struct TestMarker { +//! a: u32, +//! b: String, +//! } +//! +//! // Please see the documentation of [`ProfilerMarker`]. +//! impl gecko_profiler::ProfilerMarker for TestMarker { +//! fn marker_type_name() -> &'static str { +//! "marker type from rust" +//! } +//! fn marker_type_display() -> gecko_profiler::MarkerSchema { +//! use gecko_profiler::marker::schema::*; +//! let mut schema = MarkerSchema::new(&[Location::MarkerChart]); +//! schema.set_chart_label("Name: {marker.name}"); +//! schema.set_tooltip_label("{marker.data.a}"); +//! schema.add_key_label_format("a", "A Value", Format::Integer); +//! schema.add_key_label_format("b", "B Value", Format::String); +//! schema +//! } +//! fn stream_json_marker_data(&self, json_writer: &mut gecko_profiler::JSONWriter) { +//! json_writer.int_property("a", self.a.into()); +//! json_writer.string_property("b", &self.b); +//! } +//! } +//! ``` +//! +//! Once you've created this payload and implemented the [`ProfilerMarker`], you +//! can now add this marker in the code that you would like to measure. E.g.: +//! +//! ``` +//! gecko_profiler::add_marker( +//! // Name of the marker as a string. +//! "Marker Name", +//! // Category with an optional sub-category. +//! gecko_profiler_category!(Graphics, DisplayListBuilding), +//! // MarkerOptions that keeps options like marker timing and marker stack. +//! Default::default(), +//! // Marker payload. +//! TestMarker {a: 12, b: "hello".to_owned()}, +//! ); +//! ``` + +pub(crate) mod deserializer_tags_state; +pub mod options; +pub mod schema; + +pub use options::*; +pub use schema::MarkerSchema; + +use crate::gecko_bindings::{bindings, profiling_categories::ProfilingCategoryPair}; +use crate::json_writer::JSONWriter; +use crate::marker::deserializer_tags_state::get_or_insert_deserializer_tag; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use std::os::raw::c_char; + +/// Marker API to add a new simple marker without any payload. +/// Please see the module documentation on how to add a marker with this API. +pub fn add_untyped_marker(name: &str, category: ProfilingCategoryPair, mut options: MarkerOptions) { + if !crate::profiler_state::can_accept_markers() { + // Nothing to do. + return; + } + + unsafe { + bindings::gecko_profiler_add_marker_untyped( + name.as_ptr() as *const c_char, + name.len(), + category.to_cpp_enum_value(), + options.timing.0.as_mut_ptr(), + options.stack, + ) + } +} + +/// Marker API to add a new marker with additional text for details. +/// Please see the module documentation on how to add a marker with this API. +pub fn add_text_marker( + name: &str, + category: ProfilingCategoryPair, + mut options: MarkerOptions, + text: &str, +) { + if !crate::profiler_state::can_accept_markers() { + // Nothing to do. + return; + } + + unsafe { + bindings::gecko_profiler_add_marker_text( + name.as_ptr() as *const c_char, + name.len(), + category.to_cpp_enum_value(), + options.timing.0.as_mut_ptr(), + options.stack, + text.as_ptr() as *const c_char, + text.len(), + ) + } +} + +/// Trait that every profiler marker payload struct needs to implement. +/// This will tell the profiler back-end how to serialize it as json and +/// the front-end how to display the marker. +/// Please also see the documentation here: +/// https://firefox-source-docs.mozilla.org/tools/profiler/markers-guide.html#how-to-define-new-marker-types +/// +/// - `marker_type_name`: Returns a static string as the marker type name. This +/// should be unique and it is used to keep track of the type of markers in the +/// profiler storage, and to identify them uniquely on the profiler front-end. +/// - `marker_type_display`: Where and how to display the marker and its data. +/// Returns a `MarkerSchema` object which will be forwarded to the profiler +/// front-end. +/// - `stream_json_marker_data`: Data specific to this marker type should be +/// serialized to JSON for the profiler front-end. All the common marker data +/// like marker name, category, timing will be serialized automatically. But +/// marker specific data should be serialized here. +pub trait ProfilerMarker: Serialize + DeserializeOwned { + /// A static method that returns the name of the marker type. + fn marker_type_name() -> &'static str; + /// A static method that returns a `MarkerSchema`, which contains all the + /// information needed to stream the display schema associated with a + /// marker type. + fn marker_type_display() -> MarkerSchema; + /// A method that streams the marker payload data as JSON object properties. + /// Please see the [JSONWriter] struct to see its methods. + fn stream_json_marker_data(&self, json_writer: &mut JSONWriter); +} + +/// A function that deserializes the marker payload and streams it to the JSON. +unsafe fn transmute_and_stream<T>( + payload: *const u8, + payload_size: usize, + json_writer: &mut JSONWriter, +) where + T: ProfilerMarker, +{ + let payload_slice = std::slice::from_raw_parts(payload, payload_size); + let payload: T = bincode::deserialize(&payload_slice).unwrap(); + payload.stream_json_marker_data(json_writer); +} + +/// Main marker API to add a new marker to profiler buffer. +/// Please see the module documentation on how to add a marker with this API. +pub fn add_marker<T>( + name: &str, + category: ProfilingCategoryPair, + mut options: MarkerOptions, + payload: T, +) where + T: ProfilerMarker, +{ + if !crate::profiler_state::can_accept_markers() { + // Nothing to do. + return; + } + + let encoded_payload: Vec<u8> = bincode::serialize(&payload).unwrap(); + let payload_size = encoded_payload.len(); + let maker_tag = get_or_insert_deserializer_tag::<T>(); + + unsafe { + bindings::gecko_profiler_add_marker( + name.as_ptr() as *const c_char, + name.len(), + category.to_cpp_enum_value(), + options.timing.0.as_mut_ptr(), + options.stack, + maker_tag, + encoded_payload.as_ptr(), + payload_size, + ) + } +} + +/// Tracing marker type for Rust code. +/// This must be kept in sync with the `mozilla::baseprofiler::markers::Tracing` +/// C++ counterpart. +#[derive(Serialize, Deserialize, Debug)] +pub struct Tracing(pub String); + +impl ProfilerMarker for Tracing { + fn marker_type_name() -> &'static str { + "tracing" + } + + fn stream_json_marker_data(&self, json_writer: &mut JSONWriter) { + if self.0.len() != 0 { + json_writer.string_property("category", &self.0); + } + } + + // Tracing marker is a bit special because we have the same schema in the + // C++ side. This function will only get called when no Tracing markers are + // generated from the C++ side. But, most of the time, this will not be called + // when there is another C++ Tracing marker. + fn marker_type_display() -> MarkerSchema { + use crate::marker::schema::*; + let mut schema = MarkerSchema::new(&[ + Location::MarkerChart, + Location::MarkerTable, + Location::TimelineOverview, + ]); + schema.add_key_label_format("category", "Type", Format::String); + schema + } +} diff --git a/tools/profiler/rust-api/src/marker/options.rs b/tools/profiler/rust-api/src/marker/options.rs new file mode 100644 index 0000000000..a5d4e11094 --- /dev/null +++ b/tools/profiler/rust-api/src/marker/options.rs @@ -0,0 +1,138 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! Different options for the marker API. +//! See [`MarkerOptions`] and its fields. + +use crate::gecko_bindings::{bindings, structs::mozilla}; +use crate::ProfilerTime; +use std::mem::MaybeUninit; + +/// Marker option that contains marker timing information. +/// This class encapsulates the logic for correctly storing a marker based on its +/// constructor types. Use the static methods to create the MarkerTiming. This is +/// a transient object that is being used to enforce the constraints of the +/// combinations of the data. +/// +/// Implementation details: This is a RAII object that constructs and destroys a +/// C++ MarkerTiming object pointed to a specified reference. It allocates the +/// marker timing on stack and it's safe to move around because it's a +/// trivially-copyable object that only contains a few numbers. +#[derive(Debug)] +pub struct MarkerTiming(pub(crate) MaybeUninit<mozilla::MarkerTiming>); + +impl MarkerTiming { + /// Instant marker timing at a specific time. + pub fn instant_at(time: ProfilerTime) -> MarkerTiming { + let mut marker_timing = MaybeUninit::<mozilla::MarkerTiming>::uninit(); + unsafe { + bindings::gecko_profiler_construct_marker_timing_instant_at( + marker_timing.as_mut_ptr(), + &time.0, + ); + } + MarkerTiming(marker_timing) + } + + /// Instant marker timing at this time. + pub fn instant_now() -> MarkerTiming { + let mut marker_timing = MaybeUninit::<mozilla::MarkerTiming>::uninit(); + unsafe { + bindings::gecko_profiler_construct_marker_timing_instant_now( + marker_timing.as_mut_ptr(), + ); + } + MarkerTiming(marker_timing) + } + + /// Interval marker timing with start and end times. + pub fn interval(start_time: ProfilerTime, end_time: ProfilerTime) -> MarkerTiming { + let mut marker_timing = MaybeUninit::<mozilla::MarkerTiming>::uninit(); + unsafe { + bindings::gecko_profiler_construct_marker_timing_interval( + marker_timing.as_mut_ptr(), + &start_time.0, + &end_time.0, + ); + } + MarkerTiming(marker_timing) + } + + /// Interval marker with a start time and end time as "now". + pub fn interval_until_now_from(start_time: ProfilerTime) -> MarkerTiming { + let mut marker_timing = MaybeUninit::<mozilla::MarkerTiming>::uninit(); + unsafe { + bindings::gecko_profiler_construct_marker_timing_interval_until_now_from( + marker_timing.as_mut_ptr(), + &start_time.0, + ); + } + MarkerTiming(marker_timing) + } + + /// Interval start marker with only start time. This is a partial marker and + /// it requires another marker with `instant_end` to be complete. + pub fn interval_start(time: ProfilerTime) -> MarkerTiming { + let mut marker_timing = MaybeUninit::<mozilla::MarkerTiming>::uninit(); + unsafe { + bindings::gecko_profiler_construct_marker_timing_interval_start( + marker_timing.as_mut_ptr(), + &time.0, + ); + } + MarkerTiming(marker_timing) + } + + /// Interval end marker with only end time. This is a partial marker and + /// it requires another marker with `interval_start` to be complete. + pub fn interval_end(time: ProfilerTime) -> MarkerTiming { + let mut marker_timing = MaybeUninit::<mozilla::MarkerTiming>::uninit(); + unsafe { + bindings::gecko_profiler_construct_marker_timing_interval_end( + marker_timing.as_mut_ptr(), + &time.0, + ); + } + MarkerTiming(marker_timing) + } +} + +impl Default for MarkerTiming { + fn default() -> Self { + MarkerTiming::instant_now() + } +} + +impl Drop for MarkerTiming { + fn drop(&mut self) { + unsafe { + bindings::gecko_profiler_destruct_marker_timing(self.0.as_mut_ptr()); + } + } +} + +/// Marker option that contains marker stack information. +pub type MarkerStack = mozilla::StackCaptureOptions; + +impl Default for MarkerStack { + fn default() -> Self { + MarkerStack::NoStack + } +} + +/// This class combines each of the possible marker options above. +/// Use Default::default() for the options that you don't want to provide or the +/// options you want to leave as default. Example usage: +/// +/// ```rust +/// MarkerOptions { +/// timing: MarkerTiming::instant_now(), +/// ..Default::default() +/// } +/// ``` +#[derive(Debug, Default)] +pub struct MarkerOptions { + pub timing: MarkerTiming, + pub stack: MarkerStack, +} diff --git a/tools/profiler/rust-api/src/marker/schema.rs b/tools/profiler/rust-api/src/marker/schema.rs new file mode 100644 index 0000000000..9368582f11 --- /dev/null +++ b/tools/profiler/rust-api/src/marker/schema.rs @@ -0,0 +1,233 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! [`MarkerSchema`] and other enums that will be used by `MarkerSchema`. + +use crate::gecko_bindings::{bindings, structs::mozilla}; +use std::mem::MaybeUninit; +use std::ops::DerefMut; +use std::os::raw::c_char; +use std::pin::Pin; + +/// Marker locations to be displayed in the profiler front-end. +pub type Location = mozilla::MarkerSchema_Location; + +/// Formats of marker properties for profiler front-end. +pub type Format = mozilla::MarkerSchema_Format; + +/// Whether it's searchable or not in the profiler front-end. +pub type Searchable = mozilla::MarkerSchema_Searchable; + +/// This object collects all the information necessary to stream the JSON schema +/// that informs the front-end how to display a type of markers. +/// It will be created and populated in `marker_type_display()` functions in each +/// marker type definition, see add/set functions. +/// +/// It's a RAII object that constructs and destroys a C++ MarkerSchema object +/// pointed to a specified reference. +pub struct MarkerSchema { + pub(crate) pin: Pin<Box<MaybeUninit<mozilla::MarkerSchema>>>, +} + +impl MarkerSchema { + // Initialize a marker schema with the given `Location`s. + pub fn new(locations: &[Location]) -> Self { + let mut marker_schema = Box::pin(std::mem::MaybeUninit::<mozilla::MarkerSchema>::uninit()); + + unsafe { + bindings::gecko_profiler_construct_marker_schema( + marker_schema.deref_mut().as_mut_ptr(), + locations.as_ptr(), + locations.len(), + ); + } + MarkerSchema { pin: marker_schema } + } + + /// Marker schema for types that have special frontend handling. + /// Nothing else should be set in this case. + pub fn new_with_special_frontend_location() -> Self { + let mut marker_schema = Box::pin(std::mem::MaybeUninit::<mozilla::MarkerSchema>::uninit()); + unsafe { + bindings::gecko_profiler_construct_marker_schema_with_special_front_end_location( + marker_schema.deref_mut().as_mut_ptr(), + ); + } + MarkerSchema { pin: marker_schema } + } + + /// Optional label in the marker chart. + /// If not provided, the marker "name" will be used. The given string + /// can contain element keys in braces to include data elements streamed by + /// `stream_json_marker_data()`. E.g.: "This is {marker.data.text}" + pub fn set_chart_label(&mut self, label: &str) -> &mut Self { + unsafe { + bindings::gecko_profiler_marker_schema_set_chart_label( + self.pin.deref_mut().as_mut_ptr(), + label.as_ptr() as *const c_char, + label.len(), + ); + } + self + } + + /// Optional label in the marker chart tooltip. + /// If not provided, the marker "name" will be used. The given string + /// can contain element keys in braces to include data elements streamed by + /// `stream_json_marker_data()`. E.g.: "This is {marker.data.text}" + pub fn set_tooltip_label(&mut self, label: &str) -> &mut Self { + unsafe { + bindings::gecko_profiler_marker_schema_set_tooltip_label( + self.pin.deref_mut().as_mut_ptr(), + label.as_ptr() as *const c_char, + label.len(), + ); + } + self + } + + /// Optional label in the marker table. + /// If not provided, the marker "name" will be used. The given string + /// can contain element keys in braces to include data elements streamed by + /// `stream_json_marker_data()`. E.g.: "This is {marker.data.text}" + pub fn set_table_label(&mut self, label: &str) -> &mut Self { + unsafe { + bindings::gecko_profiler_marker_schema_set_table_label( + self.pin.deref_mut().as_mut_ptr(), + label.as_ptr() as *const c_char, + label.len(), + ); + } + self + } + + /// Set all marker chart / marker tooltip / marker table labels with the same text. + /// Same as the individual methods, the given string can contain element keys + /// in braces to include data elements streamed by `stream_json_marker_data()`. + /// E.g.: "This is {marker.data.text}" + pub fn set_all_labels(&mut self, label: &str) -> &mut Self { + unsafe { + bindings::gecko_profiler_marker_schema_set_all_labels( + self.pin.deref_mut().as_mut_ptr(), + label.as_ptr() as *const c_char, + label.len(), + ); + } + self + } + + // Each data element that is streamed by `stream_json_marker_data()` can be + // displayed as indicated by using one of the `add_...` function below. + // Each `add...` will add a line in the full marker description. Parameters: + // - `key`: Element property name as streamed by `stream_json_marker_data()`. + // - `label`: Optional label. Defaults to the key name. + // - `format`: How to format the data element value, see `Format` above. + // - `searchable`: Optional, indicates if the value is used in searches, + // defaults to false. + + /// Add a key / format row for the marker data element. + /// - `key`: Element property name as streamed by `stream_json_marker_data()`. + /// - `format`: How to format the data element value, see `Format` above. + pub fn add_key_format(&mut self, key: &str, format: Format) -> &mut Self { + unsafe { + bindings::gecko_profiler_marker_schema_add_key_format( + self.pin.deref_mut().as_mut_ptr(), + key.as_ptr() as *const c_char, + key.len(), + format, + ); + } + self + } + + /// Add a key / label / format row for the marker data element. + /// - `key`: Element property name as streamed by `stream_json_marker_data()`. + /// - `label`: Optional label. Defaults to the key name. + /// - `format`: How to format the data element value, see `Format` above. + pub fn add_key_label_format(&mut self, key: &str, label: &str, format: Format) -> &mut Self { + unsafe { + bindings::gecko_profiler_marker_schema_add_key_label_format( + self.pin.deref_mut().as_mut_ptr(), + key.as_ptr() as *const c_char, + key.len(), + label.as_ptr() as *const c_char, + label.len(), + format, + ); + } + self + } + + /// Add a key / format / searchable row for the marker data element. + /// - `key`: Element property name as streamed by `stream_json_marker_data()`. + /// - `format`: How to format the data element value, see `Format` above. + pub fn add_key_format_searchable( + &mut self, + key: &str, + format: Format, + searchable: Searchable, + ) -> &mut Self { + unsafe { + bindings::gecko_profiler_marker_schema_add_key_format_searchable( + self.pin.deref_mut().as_mut_ptr(), + key.as_ptr() as *const c_char, + key.len(), + format, + searchable, + ); + } + self + } + + /// Add a key / label / format / searchable row for the marker data element. + /// - `key`: Element property name as streamed by `stream_json_marker_data()`. + /// - `label`: Optional label. Defaults to the key name. + /// - `format`: How to format the data element value, see `Format` above. + /// - `searchable`: Optional, indicates if the value is used in searches, + /// defaults to false. + pub fn add_key_label_format_searchable( + &mut self, + key: &str, + label: &str, + format: Format, + searchable: Searchable, + ) -> &mut Self { + unsafe { + bindings::gecko_profiler_marker_schema_add_key_label_format_searchable( + self.pin.deref_mut().as_mut_ptr(), + key.as_ptr() as *const c_char, + key.len(), + label.as_ptr() as *const c_char, + label.len(), + format, + searchable, + ); + } + self + } + + /// Add a key / value static row. + /// - `key`: Element property name as streamed by `stream_json_marker_data()`. + /// - `value`: Static value to display. + pub fn add_static_label_value(&mut self, label: &str, value: &str) -> &mut Self { + unsafe { + bindings::gecko_profiler_marker_schema_add_static_label_value( + self.pin.deref_mut().as_mut_ptr(), + label.as_ptr() as *const c_char, + label.len(), + value.as_ptr() as *const c_char, + value.len(), + ); + } + self + } +} + +impl Drop for MarkerSchema { + fn drop(&mut self) { + unsafe { + bindings::gecko_profiler_destruct_marker_schema(self.pin.deref_mut().as_mut_ptr()); + } + } +} diff --git a/tools/profiler/rust-api/src/profiler_state.rs b/tools/profiler/rust-api/src/profiler_state.rs new file mode 100644 index 0000000000..c4858db22c --- /dev/null +++ b/tools/profiler/rust-api/src/profiler_state.rs @@ -0,0 +1,81 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! Gecko profiler state. + +/// Whether the Gecko profiler is currently active. +/// A typical use of this API: +/// ```rust +/// if gecko_profiler::is_active() { +/// // do something. +/// } +/// ``` +/// +/// This implementation must be kept in sync with +/// `mozilla::profiler::detail::RacyFeatures::IsActive`. +#[cfg(feature = "enabled")] +#[inline] +pub fn is_active() -> bool { + use crate::gecko_bindings::structs::mozilla::profiler::detail; + + let active_and_features = get_active_and_features(); + (active_and_features & detail::RacyFeatures_Active) != 0 +} + +/// Always false when MOZ_GECKO_PROFILER is not defined. +#[cfg(not(feature = "enabled"))] +#[inline] +pub fn is_active() -> bool { + false +} + +/// Whether the Gecko Profiler can accept markers. +/// Similar to `is_active`, but with some extra checks that determine if the +/// profiler would currently store markers. So this should be used before +/// doing some potentially-expensive work that's used in a marker. E.g.: +/// +/// ```rust +/// if gecko_profiler::can_accept_markers() { +/// // Do something expensive and add the marker with that data. +/// } +/// ``` +/// +/// This implementation must be kept in sync with +/// `mozilla::profiler::detail::RacyFeatures::IsActiveAndUnpaused`. +#[cfg(feature = "enabled")] +#[inline] +pub fn can_accept_markers() -> bool { + use crate::gecko_bindings::structs::mozilla::profiler::detail; + + let active_and_features = get_active_and_features(); + (active_and_features & detail::RacyFeatures_Active) != 0 + && (active_and_features & detail::RacyFeatures_Paused) == 0 +} + +/// Always false when MOZ_GECKO_PROFILER is not defined. +#[cfg(not(feature = "enabled"))] +#[inline] +pub fn can_accept_markers() -> bool { + false +} + +/// Returns the value of atomic `RacyFeatures::sActiveAndFeatures` from the C++ side. +#[cfg(feature = "enabled")] +#[inline] +fn get_active_and_features() -> u32 { + use crate::gecko_bindings::structs::mozilla::profiler::detail; + use std::sync::atomic::{AtomicU32, Ordering}; + + // This is reaching for the C++ atomic value instead of calling an FFI + // function to return this value. Because, calling an FFI function is much + // more expensive compared to this method. That's why it's worth to go with + // this solution for performance. But it's crucial to keep the implementation + // of this and the callers in sync with the C++ counterparts. + let active_and_features: &AtomicU32 = unsafe { + let ptr: *const u32 = std::ptr::addr_of!(detail::RacyFeatures_sActiveAndFeatures); + // TODO: Switch this to use `AtomicU32::from_ptr` once our Rust MSRV is at least 1.75.0 + &*ptr.cast() + }; + active_and_features.load(Ordering::Relaxed) +} diff --git a/tools/profiler/rust-api/src/thread.rs b/tools/profiler/rust-api/src/thread.rs new file mode 100644 index 0000000000..353469a4bb --- /dev/null +++ b/tools/profiler/rust-api/src/thread.rs @@ -0,0 +1,23 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +///! Profiler API for thread registration and unregistration. +use crate::gecko_bindings::bindings; +use std::ffi::CString; + +/// Register a thread with the Gecko Profiler. +pub fn register_thread(thread_name: &str) { + let name = CString::new(thread_name).unwrap(); + unsafe { + // gecko_profiler_register_thread copies the passed name here. + bindings::gecko_profiler_register_thread(name.as_ptr()); + } +} + +/// Unregister a thread with the Gecko Profiler. +pub fn unregister_thread() { + unsafe { + bindings::gecko_profiler_unregister_thread(); + } +} diff --git a/tools/profiler/rust-api/src/time.rs b/tools/profiler/rust-api/src/time.rs new file mode 100644 index 0000000000..56315690c9 --- /dev/null +++ b/tools/profiler/rust-api/src/time.rs @@ -0,0 +1,71 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! Gecko profiler time. + +use crate::gecko_bindings::{bindings, structs::mozilla}; +use std::mem::MaybeUninit; + +/// Profiler time for the marker API. +/// This should be used as the `MarkerTiming` parameter. +/// E.g.: +/// +/// ``` +/// let start = ProfilerTime::now(); +/// // ...some code... +/// gecko_profiler::add_untyped_marker( +/// "marker name", +/// category, +/// MarkerOptions { +/// timing: MarkerTiming::interval_until_now_from(start), +/// ..Default::default() +/// }, +/// ); +/// ``` +#[derive(Debug)] +pub struct ProfilerTime(pub(crate) mozilla::TimeStamp); + +impl ProfilerTime { + pub fn now() -> ProfilerTime { + let mut marker_timing = MaybeUninit::<mozilla::TimeStamp>::uninit(); + unsafe { + bindings::gecko_profiler_construct_timestamp_now(marker_timing.as_mut_ptr()); + ProfilerTime(marker_timing.assume_init()) + } + } + + pub fn add_microseconds(self, microseconds: f64) -> Self { + let mut dest = MaybeUninit::<mozilla::TimeStamp>::uninit(); + unsafe { + bindings::gecko_profiler_add_timestamp(&self.0, dest.as_mut_ptr(), microseconds); + ProfilerTime(dest.assume_init()) + } + } + + pub fn subtract_microseconds(self, microseconds: f64) -> Self { + let mut dest = MaybeUninit::<mozilla::TimeStamp>::uninit(); + unsafe { + bindings::gecko_profiler_subtract_timestamp(&self.0, dest.as_mut_ptr(), microseconds); + ProfilerTime(dest.assume_init()) + } + } +} + +impl Clone for ProfilerTime { + fn clone(&self) -> Self { + let mut dest = MaybeUninit::<mozilla::TimeStamp>::uninit(); + unsafe { + bindings::gecko_profiler_clone_timestamp(&self.0, dest.as_mut_ptr()); + ProfilerTime(dest.assume_init()) + } + } +} + +impl Drop for ProfilerTime { + fn drop(&mut self) { + unsafe { + bindings::gecko_profiler_destruct_timestamp(&mut self.0); + } + } +} |