summaryrefslogtreecommitdiffstats
path: root/tools/profiler/rust-api
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--tools/profiler/rust-api/Cargo.toml23
-rw-r--r--tools/profiler/rust-api/README.md5
-rw-r--r--tools/profiler/rust-api/build.rs118
-rw-r--r--tools/profiler/rust-api/cbindgen.toml15
-rw-r--r--tools/profiler/rust-api/extra-bindgen-flags.in1
-rw-r--r--tools/profiler/rust-api/macros/Cargo.toml13
-rw-r--r--tools/profiler/rust-api/macros/src/lib.rs65
-rw-r--r--tools/profiler/rust-api/src/gecko_bindings/glue.rs53
-rw-r--r--tools/profiler/rust-api/src/gecko_bindings/mod.rs21
-rw-r--r--tools/profiler/rust-api/src/gecko_bindings/profiling_categories.rs32
-rw-r--r--tools/profiler/rust-api/src/json_writer.rs86
-rw-r--r--tools/profiler/rust-api/src/label.rs137
-rw-r--r--tools/profiler/rust-api/src/lib.rs29
-rw-r--r--tools/profiler/rust-api/src/marker/deserializer_tags_state.rs116
-rw-r--r--tools/profiler/rust-api/src/marker/mod.rs284
-rw-r--r--tools/profiler/rust-api/src/marker/options.rs138
-rw-r--r--tools/profiler/rust-api/src/marker/schema.rs233
-rw-r--r--tools/profiler/rust-api/src/profiler_state.rs78
-rw-r--r--tools/profiler/rust-api/src/thread.rs23
-rw-r--r--tools/profiler/rust-api/src/time.rs71
20 files changed, 1541 insertions, 0 deletions
diff --git a/tools/profiler/rust-api/Cargo.toml b/tools/profiler/rust-api/Cargo.toml
new file mode 100644
index 0000000000..20e8f3bdec
--- /dev/null
+++ b/tools/profiler/rust-api/Cargo.toml
@@ -0,0 +1,23 @@
+[package]
+name = "gecko-profiler"
+version = "0.1.0"
+authors = ["The Mozilla Project Developers"]
+edition = "2018"
+license = "MPL-2.0"
+
+[dependencies]
+profiler-macros = { path = "./macros" }
+lazy_static = "1"
+serde = { version = "1.0", features = ["derive"] }
+bincode = "1"
+mozbuild = "0.1"
+
+[build-dependencies]
+lazy_static = "1"
+bindgen = {version = "0.63", default-features = false}
+mozbuild = "0.1"
+
+[features]
+# This feature is being set by Gecko. If it's not set, all public functions and
+# structs will be no-op.
+enabled = []
diff --git a/tools/profiler/rust-api/README.md b/tools/profiler/rust-api/README.md
new file mode 100644
index 0000000000..60926a85c7
--- /dev/null
+++ b/tools/profiler/rust-api/README.md
@@ -0,0 +1,5 @@
+# Gecko Profiler API for Rust
+
+This crate is the collection of all the API endpoints for Gecko Profiler. Please use this crate instead of using raw FFI calls.
+
+See the module documentations for more information about the specific API endpoints.
diff --git a/tools/profiler/rust-api/build.rs b/tools/profiler/rust-api/build.rs
new file mode 100644
index 0000000000..2dd70ed55c
--- /dev/null
+++ b/tools/profiler/rust-api/build.rs
@@ -0,0 +1,118 @@
+/* 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/. */
+
+//! Build script for the Gecko Profiler bindings.
+//!
+//! This file is executed by cargo when this crate is built. It generates the
+//! `$OUT_DIR/bindings.rs` file which is then included by `src/gecko_bindings/mod.rs`.
+
+#[macro_use]
+extern crate lazy_static;
+
+use bindgen::{Builder, CargoCallbacks, CodegenConfig};
+use std::env;
+use std::fs;
+use std::path::PathBuf;
+
+lazy_static! {
+ static ref OUTDIR_PATH: PathBuf = PathBuf::from(env::var_os("OUT_DIR").unwrap()).join("gecko");
+}
+
+const BINDINGS_FILE: &str = "bindings.rs";
+
+lazy_static! {
+ static ref BINDGEN_FLAGS: Vec<String> = {
+ // Load build-specific config overrides.
+ let path = mozbuild::TOPOBJDIR.join("tools/profiler/rust-api/extra-bindgen-flags");
+ println!("cargo:rerun-if-changed={}", path.to_str().unwrap());
+ fs::read_to_string(path).expect("Failed to read extra-bindgen-flags file")
+ .split_whitespace()
+ .map(std::borrow::ToOwned::to_owned)
+ .collect()
+ };
+ static ref SEARCH_PATHS: Vec<PathBuf> = vec![
+ mozbuild::TOPOBJDIR.join("dist/include"),
+ mozbuild::TOPOBJDIR.join("dist/include/nspr"),
+ ];
+}
+
+fn search_include(name: &str) -> Option<PathBuf> {
+ for path in SEARCH_PATHS.iter() {
+ let file = path.join(name);
+ if file.is_file() {
+ return Some(file);
+ }
+ }
+ None
+}
+
+fn add_include(name: &str) -> String {
+ let file = match search_include(name) {
+ Some(file) => file,
+ None => panic!("Include not found: {}", name),
+ };
+ let file_path = String::from(file.to_str().unwrap());
+ println!("cargo:rerun-if-changed={}", file_path);
+ file_path
+}
+
+fn generate_bindings() {
+ let mut builder = Builder::default()
+ .enable_cxx_namespaces()
+ .with_codegen_config(CodegenConfig::TYPES | CodegenConfig::VARS | CodegenConfig::FUNCTIONS)
+ .disable_untagged_union()
+ .size_t_is_usize(true);
+
+ for dir in SEARCH_PATHS.iter() {
+ builder = builder.clang_arg("-I").clang_arg(dir.to_str().unwrap());
+ }
+
+ builder = builder
+ .clang_arg("-include")
+ .clang_arg(add_include("mozilla-config.h"));
+
+ for item in &*BINDGEN_FLAGS {
+ builder = builder.clang_arg(item);
+ }
+
+ let bindings = builder
+ .header(add_include("GeckoProfiler.h"))
+ .header(add_include("ProfilerBindings.h"))
+ .allowlist_function("gecko_profiler_.*")
+ .allowlist_var("mozilla::profiler::detail::RacyFeatures::sActiveAndFeatures")
+ .allowlist_type("mozilla::profiler::detail::RacyFeatures")
+ .rustified_enum("mozilla::StackCaptureOptions")
+ .rustified_enum("mozilla::MarkerSchema_Location")
+ .rustified_enum("mozilla::MarkerSchema_Format")
+ .rustified_enum("mozilla::MarkerSchema_Searchable")
+ // Converting std::string to an opaque type makes some platforms build
+ // successfully. Otherwise, it fails to build because MarkerSchema has
+ // some std::strings as its fields.
+ .opaque_type("std::string")
+ // std::vector needs to be converted to an opaque type because, if it's
+ // not an opaque type, bindgen can't find its size properly and
+ // MarkerSchema's total size reduces. That causes a heap buffer overflow.
+ .opaque_type("std::vector")
+ .raw_line("pub use self::root::*;")
+ // Tell cargo to invalidate the built crate whenever any of the
+ // included header files changed.
+ .parse_callbacks(Box::new(CargoCallbacks))
+ // Finish the builder and generate the bindings.
+ .generate()
+ // Unwrap the Result and panic on failure.
+ .expect("Unable to generate bindings");
+
+ let out_file = OUTDIR_PATH.join(BINDINGS_FILE);
+ bindings
+ .write_to_file(out_file)
+ .expect("Couldn't write bindings!");
+}
+
+fn main() {
+ println!("cargo:rerun-if-changed=build.rs");
+ println!("cargo:out_dir={}", env::var("OUT_DIR").unwrap());
+
+ fs::create_dir_all(&*OUTDIR_PATH).unwrap();
+ generate_bindings();
+}
diff --git a/tools/profiler/rust-api/cbindgen.toml b/tools/profiler/rust-api/cbindgen.toml
new file mode 100644
index 0000000000..3f0df0f34f
--- /dev/null
+++ b/tools/profiler/rust-api/cbindgen.toml
@@ -0,0 +1,15 @@
+header = """/* 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/. */"""
+autogen_warning = """/* DO NOT MODIFY THIS MANUALLY! This file was generated using cbindgen. See RunCbindgen.py */
+#ifndef ProfilerRustBindings_h
+#error "Don't include this file directly, instead include ProfilerRustBindings.h"
+#endif
+"""
+include_version = true
+braces = "SameLine"
+line_length = 100
+tab_width = 2
+language = "C++"
+# Put FFI calls in the `mozilla::profiler::ffi` namespace.
+namespaces = ["mozilla", "profiler", "ffi"]
diff --git a/tools/profiler/rust-api/extra-bindgen-flags.in b/tools/profiler/rust-api/extra-bindgen-flags.in
new file mode 100644
index 0000000000..b0275a031b
--- /dev/null
+++ b/tools/profiler/rust-api/extra-bindgen-flags.in
@@ -0,0 +1 @@
+@BINDGEN_SYSTEM_FLAGS@ @NSPR_CFLAGS@
diff --git a/tools/profiler/rust-api/macros/Cargo.toml b/tools/profiler/rust-api/macros/Cargo.toml
new file mode 100644
index 0000000000..b8bd9910dc
--- /dev/null
+++ b/tools/profiler/rust-api/macros/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "profiler-macros"
+version = "0.1.0"
+authors = ["The Mozilla Project Developers"]
+edition = "2018"
+license = "MPL-2.0"
+
+[lib]
+proc-macro = true
+
+[dependencies]
+syn = "1"
+quote = "1.0"
diff --git a/tools/profiler/rust-api/macros/src/lib.rs b/tools/profiler/rust-api/macros/src/lib.rs
new file mode 100644
index 0000000000..48617b758e
--- /dev/null
+++ b/tools/profiler/rust-api/macros/src/lib.rs
@@ -0,0 +1,65 @@
+/* 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/. */
+
+#![deny(warnings)]
+
+//! A procedural macro as a syntactical sugar to `gecko_profiler_label!` macro.
+//! You can use this macro on top of functions to automatically append the
+//! label frame to the function.
+//!
+//! Example usage:
+//! ```rust
+//! #[gecko_profiler_fn_label(DOM)]
+//! fn foo(bar: u32) -> u32 {
+//! bar
+//! }
+//!
+//! #[gecko_profiler_fn_label(Javascript, IonMonkey)]
+//! pub fn bar(baz: i8) -> i8 {
+//! baz
+//! }
+//! ```
+//!
+//! See the documentation of `gecko_profiler_label!` macro to learn more about
+//! its parameters.
+
+extern crate proc_macro;
+
+use proc_macro::TokenStream;
+use quote::quote;
+use syn::{parse_macro_input, AttributeArgs, ItemFn};
+
+#[proc_macro_attribute]
+pub fn gecko_profiler_fn_label(attrs: TokenStream, input: TokenStream) -> TokenStream {
+ let attr_args = parse_macro_input!(attrs as AttributeArgs);
+ let input = parse_macro_input!(input as ItemFn);
+
+ if attr_args.is_empty() || attr_args.len() > 2 {
+ panic!("Expected one or two arguments as ProfilingCategory or ProfilingCategoryPair but {} arguments provided!", attr_args.len());
+ }
+
+ let category_name = &attr_args[0];
+ // Try to get the subcategory if possible. Otherwise, use `None`.
+ let subcategory_if_provided = match attr_args.get(1) {
+ Some(subcategory) => quote!(, #subcategory),
+ None => quote!(),
+ };
+
+ let ItemFn {
+ attrs,
+ vis,
+ sig,
+ block,
+ } = input;
+ let stmts = &block.stmts;
+
+ let new_fn = quote! {
+ #(#attrs)* #vis #sig {
+ gecko_profiler_label!(#category_name#subcategory_if_provided);
+ #(#stmts)*
+ }
+ };
+
+ new_fn.into()
+}
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..8ab6f2ed99
--- /dev/null
+++ b/tools/profiler/rust-api/src/json_writer.rs
@@ -0,0 +1,86 @@
+/* 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 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..3c857ae8ac
--- /dev/null
+++ b/tools/profiler/rust-api/src/lib.rs
@@ -0,0 +1,29 @@
+/* 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::*;
+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..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
+ }
+}
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..0d5359684d
--- /dev/null
+++ b/tools/profiler/rust-api/src/profiler_state.rs
@@ -0,0 +1,78 @@
+/* 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::mem;
+ 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.
+ unsafe { mem::transmute::<_, &AtomicU32>(&detail::RacyFeatures_sActiveAndFeatures) }
+ .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);
+ }
+ }
+}