summaryrefslogtreecommitdiffstats
path: root/tools/profiler/rust-api/src/marker/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'tools/profiler/rust-api/src/marker/mod.rs')
-rw-r--r--tools/profiler/rust-api/src/marker/mod.rs284
1 files changed, 284 insertions, 0 deletions
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..984a475089
--- /dev/null
+++ b/tools/profiler/rust-api/src/marker/mod.rs
@@ -0,0 +1,284 @@
+/* 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 crate::marker::options::MarkerOptions;
+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() -> schema::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() -> schema::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
+ }
+}