From 91df7ef220caea2cef4d2c85c59e902514c70dd2 Mon Sep 17 00:00:00 2001 From: Georgiy Tugai Date: Wed, 29 Apr 2026 17:01:54 +0200 Subject: [PATCH] add optional `profiling` instrumentation to library crates Gate the `profiling` crate (v1.0, default-features off) behind a `profiling` feature in all five library crates. Annotate 36 public entry points with `profiling::function_scope!()` so users get zero-cost, opt-in structured spans for puffin/tracy/tracing backends. --- Cargo.lock | 11 ++++++++ Cargo.toml | 1 + crates/message-format-compiler/Cargo.toml | 4 +++ .../src/compile/mod.rs | 20 ++++++++++++++ .../message-format-resource-json/Cargo.toml | 4 +++ .../message-format-resource-json/src/lib.rs | 6 +++++ .../message-format-resource-toml/Cargo.toml | 4 +++ .../message-format-resource-toml/src/lib.rs | 2 ++ crates/message-format-runtime/Cargo.toml | 2 ++ crates/message-format-runtime/src/builtin.rs | 2 ++ crates/message-format-runtime/src/catalog.rs | 2 ++ .../message-format-runtime/src/formatter.rs | 12 +++++++++ crates/message-format/Cargo.toml | 2 ++ crates/message-format/src/catalog.rs | 26 +++++++++++++++++++ crates/message-format/src/formatter.rs | 6 +++++ 15 files changed, 104 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index a536860..3f994e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -775,6 +775,7 @@ dependencies = [ "icu_locale_core", "message-format-compiler", "message-format-runtime", + "profiling", ] [[package]] @@ -793,6 +794,7 @@ dependencies = [ "hashbrown", "icu_normalizer", "message-format-runtime", + "profiling", "serde", "toml 0.9.12+spec-1.1.0", ] @@ -827,6 +829,7 @@ version = "0.1.0" dependencies = [ "json-spanned-value", "message-format-compiler", + "profiling", ] [[package]] @@ -834,6 +837,7 @@ name = "message-format-resource-toml" version = "0.1.0" dependencies = [ "message-format-compiler", + "profiling", "toml 1.0.6+spec-1.1.0", ] @@ -848,6 +852,7 @@ dependencies = [ "icu_locale", "icu_locale_core", "icu_plurals", + "profiling", ] [[package]] @@ -956,6 +961,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "profiling" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" + [[package]] name = "quote" version = "1.0.44" diff --git a/Cargo.toml b/Cargo.toml index e392f28..8b86d2f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ message-format-compiler = { path = "crates/message-format-compiler", version = " message-format-resource-json = { path = "crates/message-format-resource-json", version = "0.1.0" } message-format-resource-toml = { path = "crates/message-format-resource-toml", version = "0.1.0" } message-format-runtime = { path = "crates/message-format-runtime", version = "0.1.0" } +profiling = { version = "1.0", default-features = false } [workspace.lints] # LINEBENDER LINT SET - Cargo.toml - v8 diff --git a/crates/message-format-compiler/Cargo.toml b/crates/message-format-compiler/Cargo.toml index c1ee87e..1bf7c8a 100644 --- a/crates/message-format-compiler/Cargo.toml +++ b/crates/message-format-compiler/Cargo.toml @@ -18,8 +18,12 @@ hashbrown = { default-features = false, features = [ ], version = "0.17.0" } icu_normalizer = { default-features = false, features = ["compiled_data"], version = "2.1.1" } message-format-runtime = { workspace = true } +profiling = { optional = true, workspace = true } serde = { features = ["derive"], version = "1.0" } toml = "0.9" +[features] +profiling = ["dep:profiling", "message-format-runtime/profiling"] + [lints] workspace = true diff --git a/crates/message-format-compiler/src/compile/mod.rs b/crates/message-format-compiler/src/compile/mod.rs index b426254..fa18cbd 100644 --- a/crates/message-format-compiler/src/compile/mod.rs +++ b/crates/message-format-compiler/src/compile/mod.rs @@ -424,6 +424,8 @@ impl CatalogBuilder { /// Parse one MF2 message body and add it under its explicit message id. pub fn add_input(&mut self, input: CompileInput<'_>) -> Result { + #[cfg(feature = "profiling")] + profiling::function_scope!(); let source_id = self .register_source(input.name, input.kind) .map_err(|error| BuildError::error(None, error))?; @@ -461,6 +463,8 @@ impl CatalogBuilder { /// Parse one resource/container input made up of named MF2 message bodies. pub fn add_resource_input(&mut self, input: ResourceInput) -> Result { + #[cfg(feature = "profiling")] + profiling::function_scope!(); let source_name = input.name.clone(); let source_kind = input.kind.clone(); let source_id = self @@ -515,6 +519,8 @@ impl CatalogBuilder { /// Compile into a binary catalog payload plus provenance sidecar data. pub fn compile(self) -> CompileReport { + #[cfg(feature = "profiling")] + profiling::function_scope!(); let Self { options, sources, @@ -559,6 +565,8 @@ impl CatalogBuilder { /// assert!(!bytes.is_empty()); /// ``` pub fn compile_str(source: &str) -> Result, CompileError> { + #[cfg(feature = "profiling")] + profiling::function_scope!(); compile(source, CompileOptions::default()) } @@ -575,6 +583,8 @@ pub fn compile_str(source: &str) -> Result, CompileError> { /// assert!(!bytes.is_empty()); /// ``` pub fn compile(source: &str, options: CompileOptions) -> Result, CompileError> { + #[cfg(feature = "profiling")] + profiling::function_scope!(); let message = parse_single_message_with_source(source, options, Some(SourceId(0)))?; compile_parsed_messages(vec![message], None, options) } @@ -588,6 +598,8 @@ pub fn compile_with_manifest( options: CompileOptions, manifest: &FunctionManifest, ) -> Result, CompileError> { + #[cfg(feature = "profiling")] + profiling::function_scope!(); let message = parse_single_message_with_source(source, options, Some(SourceId(0)))?; compile_parsed_messages(vec![message], Some(manifest), options) } @@ -597,6 +609,8 @@ pub fn compile_inputs<'a>( inputs: impl IntoIterator>, options: CompileOptions, ) -> CompileReport { + #[cfg(feature = "profiling")] + profiling::function_scope!(); let mut builder = CatalogBuilder::with_options(options); let mut diagnostics = Vec::new(); for input in inputs { @@ -612,6 +626,8 @@ pub fn compile_resources( inputs: impl IntoIterator, options: CompileOptions, ) -> CompileReport { + #[cfg(feature = "profiling")] + profiling::function_scope!(); let mut builder = CatalogBuilder::with_options(options); let mut diagnostics = Vec::new(); for input in inputs { @@ -626,6 +642,8 @@ pub fn compile_inputs_with_manifest<'a>( options: CompileOptions, manifest: &FunctionManifest, ) -> CompileReport { + #[cfg(feature = "profiling")] + profiling::function_scope!(); let mut builder = CatalogBuilder::with_options(options); builder.set_function_manifest(manifest.clone()); let mut diagnostics = Vec::new(); @@ -643,6 +661,8 @@ pub fn compile_resources_with_manifest( options: CompileOptions, manifest: &FunctionManifest, ) -> CompileReport { + #[cfg(feature = "profiling")] + profiling::function_scope!(); let mut builder = CatalogBuilder::with_options(options); builder.set_function_manifest(manifest.clone()); let mut diagnostics = Vec::new(); diff --git a/crates/message-format-resource-json/Cargo.toml b/crates/message-format-resource-json/Cargo.toml index 827f3e7..15890ed 100644 --- a/crates/message-format-resource-json/Cargo.toml +++ b/crates/message-format-resource-json/Cargo.toml @@ -14,6 +14,10 @@ description = "JSON resource adapters for message-format compiler resource input [dependencies] json-spanned-value = "0.2.2" message-format-compiler.workspace = true +profiling = { optional = true, workspace = true } + +[features] +profiling = ["dep:profiling", "message-format-compiler/profiling"] [lints] workspace = true diff --git a/crates/message-format-resource-json/src/lib.rs b/crates/message-format-resource-json/src/lib.rs index 73d4270..374cea0 100644 --- a/crates/message-format-resource-json/src/lib.rs +++ b/crates/message-format-resource-json/src/lib.rs @@ -68,6 +68,8 @@ pub fn parse_json_resource( source: &str, profile: JsonProfile, ) -> Result { + #[cfg(feature = "profiling")] + profiling::function_scope!(); let root: JsonValue = json_spanned_value::from_str(source) .map_err(|err| ResourceJsonError::from_json(source, err))?; match profile { @@ -82,6 +84,8 @@ pub fn parse_flat_json_resource( name: impl Into, source: &str, ) -> Result { + #[cfg(feature = "profiling")] + profiling::function_scope!(); parse_json_resource(name, source, JsonProfile::Flat) } @@ -91,6 +95,8 @@ pub fn parse_chrome_json_resource( name: impl Into, source: &str, ) -> Result { + #[cfg(feature = "profiling")] + profiling::function_scope!(); parse_json_resource(name, source, JsonProfile::Chrome) } diff --git a/crates/message-format-resource-toml/Cargo.toml b/crates/message-format-resource-toml/Cargo.toml index 6c3d32e..7c8593f 100644 --- a/crates/message-format-resource-toml/Cargo.toml +++ b/crates/message-format-resource-toml/Cargo.toml @@ -13,7 +13,11 @@ description = "TOML resource adapter for message-format compiler resource inputs [dependencies] message-format-compiler.workspace = true +profiling = { optional = true, workspace = true } toml = "1" +[features] +profiling = ["dep:profiling", "message-format-compiler/profiling"] + [lints] workspace = true diff --git a/crates/message-format-resource-toml/src/lib.rs b/crates/message-format-resource-toml/src/lib.rs index 87d793f..7e94395 100644 --- a/crates/message-format-resource-toml/src/lib.rs +++ b/crates/message-format-resource-toml/src/lib.rs @@ -29,6 +29,8 @@ pub fn parse_resource_toml( name: impl Into, source: &str, ) -> Result { + #[cfg(feature = "profiling")] + profiling::function_scope!(); let parsed = DeTable::parse(source).map_err(|err| ResourceTomlError::from_toml(source, err))?; let root = parsed.into_inner(); let messages = root diff --git a/crates/message-format-runtime/Cargo.toml b/crates/message-format-runtime/Cargo.toml index aa1eea8..84bd3fb 100644 --- a/crates/message-format-runtime/Cargo.toml +++ b/crates/message-format-runtime/Cargo.toml @@ -27,6 +27,7 @@ icu_locale_core = { default-features = false, optional = true, version = "2.1.1" icu_plurals = { default-features = false, features = [ "compiled_data", ], optional = true, version = "2.1.1" } +profiling = { optional = true, workspace = true } [features] default = [] @@ -39,6 +40,7 @@ icu4x = [ "dep:icu_calendar", "dep:icu_datetime", ] +profiling = ["dep:profiling"] [package.metadata.docs.rs] all-features = true diff --git a/crates/message-format-runtime/src/builtin.rs b/crates/message-format-runtime/src/builtin.rs index 02117d6..468ceff 100644 --- a/crates/message-format-runtime/src/builtin.rs +++ b/crates/message-format-runtime/src/builtin.rs @@ -313,6 +313,8 @@ impl BuiltinHost { /// Returns: /// - `FormatError::Trap(Trap::UnsupportedLocale)` when ICU plural rules are unavailable. pub fn new(locale: &Locale) -> Result { + #[cfg(feature = "profiling")] + profiling::function_scope!(); let cardinal_rules = PluralRules::try_new_cardinal(locale.into()) .map_err(|_| FormatError::Trap(Trap::UnsupportedLocale))?; let ordinal_rules = PluralRules::try_new_ordinal(locale.into()) diff --git a/crates/message-format-runtime/src/catalog.rs b/crates/message-format-runtime/src/catalog.rs index d561454..b71e419 100644 --- a/crates/message-format-runtime/src/catalog.rs +++ b/crates/message-format-runtime/src/catalog.rs @@ -38,6 +38,8 @@ pub struct Catalog { impl Catalog { /// Decode and verify a catalog payload. pub fn from_bytes(bytes: &[u8]) -> Result { + #[cfg(feature = "profiling")] + profiling::function_scope!(); if bytes.len() < HEADER_LEN { return Err(CatalogError::ChunkOutOfBounds); } diff --git a/crates/message-format-runtime/src/formatter.rs b/crates/message-format-runtime/src/formatter.rs index c9cc22f..390e63d 100644 --- a/crates/message-format-runtime/src/formatter.rs +++ b/crates/message-format-runtime/src/formatter.rs @@ -67,6 +67,8 @@ impl<'a, H: Host> Formatter<'a, H> { /// /// Calls [`Host::index`] to pre-compute catalog-specific data. pub fn new(catalog: &'a Catalog, mut host: H) -> Result { + #[cfg(feature = "profiling")] + profiling::function_scope!(); let index = host.index(catalog)?; Ok(Self { catalog, @@ -88,6 +90,8 @@ impl<'a, H: Host> Formatter<'a, H> { /// Resolve a message id to a reusable handle. pub fn resolve(&self, message_id: &str) -> Result { + #[cfg(feature = "profiling")] + profiling::function_scope!(); MessageHandle::from_catalog(self.catalog, message_id) } @@ -105,6 +109,8 @@ impl<'a, H: Host> Formatter<'a, H> { args: &dyn Args, sink: &mut S, ) -> Result, FormatError> { + #[cfg(feature = "profiling")] + profiling::function_scope!(); let mut diagnostics = VecDiagnostics::default(); run_bytecode( self.catalog, @@ -161,6 +167,8 @@ impl<'a, H: Host> MultiFormatter<'a, H> { catalogs: impl IntoIterator, mut host: H, ) -> Result { + #[cfg(feature = "profiling")] + profiling::function_scope!(); let catalogs: Box<[_]> = catalogs .into_iter() .map(|catalog| { @@ -191,6 +199,8 @@ impl<'a, H: Host> MultiFormatter<'a, H> { /// /// Returns a handle to the first catalog that contains the message. pub fn resolve(&self, message_id: &str) -> Result { + #[cfg(feature = "profiling")] + profiling::function_scope!(); for (idx, (catalog, _)) in self.catalogs.iter().enumerate() { if let Some(entry_pc) = catalog.message_pc(message_id) { return Ok(MultiMessageHandle { @@ -261,6 +271,8 @@ impl<'a, H: Host> MultiFormatter<'a, H> { args: &dyn Args, sink: &mut S, ) -> Result, FormatError> { + #[cfg(feature = "profiling")] + profiling::function_scope!(); let (catalog, index) = self .catalogs .get(message.catalog_idx as usize) diff --git a/crates/message-format/Cargo.toml b/crates/message-format/Cargo.toml index a87918a..4bc07c6 100644 --- a/crates/message-format/Cargo.toml +++ b/crates/message-format/Cargo.toml @@ -16,11 +16,13 @@ default = ["std"] std = ["message-format-runtime/std"] icu4x = ["message-format-runtime/icu4x"] compile = ["dep:message-format-compiler"] +profiling = ["dep:profiling", "message-format-runtime/profiling", "message-format-compiler?/profiling"] [dependencies] icu_locale_core = { default-features = false, version = "2.1.1" } message-format-compiler = { optional = true, workspace = true } message-format-runtime = { workspace = true } +profiling = { optional = true, workspace = true } [package.metadata.docs.rs] all-features = true diff --git a/crates/message-format/src/catalog.rs b/crates/message-format/src/catalog.rs index 13cf659..afd3980 100644 --- a/crates/message-format/src/catalog.rs +++ b/crates/message-format/src/catalog.rs @@ -34,6 +34,8 @@ pub struct MessageCatalog { impl MessageCatalog { /// Decode a serialized catalog payload. pub fn from_bytes(bytes: &[u8]) -> Result { + #[cfg(feature = "profiling")] + profiling::function_scope!(); Ok(Self { catalog: runtime::Catalog::from_bytes(bytes)?, }) @@ -68,6 +70,8 @@ impl MessageCatalog { &self, locale: &Locale, ) -> Result, runtime::FormatError> { + #[cfg(feature = "profiling")] + profiling::function_scope!(); let candidates = locale_candidates(locale); MessageFormatter::new(core::iter::once(&self.catalog), &candidates) } @@ -81,6 +85,8 @@ impl MessageCatalog { source: &str, options: crate::compiler::CompileOptions, ) -> Result { + #[cfg(feature = "profiling")] + profiling::function_scope!(); let bytes = crate::compiler::compile(source, options)?; Self::from_compiler_bytes(&bytes) } @@ -91,12 +97,16 @@ impl MessageCatalog { options: crate::compiler::CompileOptions, manifest: &crate::compiler::FunctionManifest, ) -> Result { + #[cfg(feature = "profiling")] + profiling::function_scope!(); let bytes = crate::compiler::compile_with_manifest(source, options, manifest)?; Self::from_compiler_bytes(&bytes) } /// Compile one MF2 message source string with default options and decode it into a loaded catalog. pub fn compile_str(source: &str) -> Result { + #[cfg(feature = "profiling")] + profiling::function_scope!(); let bytes = crate::compiler::compile_str(source)?; Self::from_compiler_bytes(&bytes) } @@ -106,6 +116,8 @@ impl MessageCatalog { inputs: impl IntoIterator>, options: crate::compiler::CompileOptions, ) -> Result<(Self, crate::compiler::SourceMap), crate::compiler::BuildError> { + #[cfg(feature = "profiling")] + profiling::function_scope!(); let compiled = crate::compiler::compile_inputs(inputs, options) .into_result() .map_err(first_build_error)?; @@ -125,6 +137,8 @@ impl MessageCatalog { inputs: impl IntoIterator, options: crate::compiler::CompileOptions, ) -> Result<(Self, crate::compiler::SourceMap), crate::compiler::BuildError> { + #[cfg(feature = "profiling")] + profiling::function_scope!(); let compiled = crate::compiler::compile_resources(inputs, options) .into_result() .map_err(first_build_error)?; @@ -145,6 +159,8 @@ impl MessageCatalog { options: crate::compiler::CompileOptions, manifest: &crate::compiler::FunctionManifest, ) -> Result<(Self, crate::compiler::SourceMap), crate::compiler::BuildError> { + #[cfg(feature = "profiling")] + profiling::function_scope!(); let compiled = crate::compiler::compile_inputs_with_manifest(inputs, options, manifest) .into_result() .map_err(first_build_error)?; @@ -165,6 +181,8 @@ impl MessageCatalog { options: crate::compiler::CompileOptions, manifest: &crate::compiler::FunctionManifest, ) -> Result<(Self, crate::compiler::SourceMap), crate::compiler::BuildError> { + #[cfg(feature = "profiling")] + profiling::function_scope!(); let compiled = crate::compiler::compile_resources_with_manifest(inputs, options, manifest) .into_result() .map_err(first_build_error)?; @@ -182,6 +200,8 @@ impl MessageCatalog { /// Compile a file from disk and decode it into a loaded catalog. #[cfg(feature = "std")] pub fn compile_file(path: &std::path::Path) -> Result { + #[cfg(feature = "profiling")] + profiling::function_scope!(); let source = std::fs::read_to_string(path).map_err(|err| { crate::compiler::CompileError::IoError { path: path.to_path_buf(), @@ -253,6 +273,8 @@ impl CatalogBundle { catalogs: impl IntoIterator, locale: &Locale, ) -> Result { + #[cfg(feature = "profiling")] + profiling::function_scope!(); let candidates = locale_candidates(locale); let mut slots: Vec> = vec![None; candidates.len()]; for lc in catalogs { @@ -284,6 +306,8 @@ impl CatalogBundle { locale: &Locale, mut fetch: impl FnMut(&Locale) -> Result, E>, ) -> Result> { + #[cfg(feature = "profiling")] + profiling::function_scope!(); let candidates = locale_candidates(locale); let mut catalogs = Vec::new(); for candidate in &candidates { @@ -308,6 +332,8 @@ impl CatalogBundle { /// The host locale for number/date formatting is derived from the /// target locale's CLDR fallback chain. pub fn formatter(&self) -> Result, runtime::FormatError> { + #[cfg(feature = "profiling")] + profiling::function_scope!(); MessageFormatter::new(self.catalogs.iter(), &self.candidates) } } diff --git a/crates/message-format/src/formatter.rs b/crates/message-format/src/formatter.rs index aa55cc9..02afd04 100644 --- a/crates/message-format/src/formatter.rs +++ b/crates/message-format/src/formatter.rs @@ -86,6 +86,8 @@ impl<'a> MessageFormatter<'a> { &self, message_id: &str, ) -> Result { + #[cfg(feature = "profiling")] + profiling::function_scope!(); self.inner.resolve(message_id) } @@ -100,6 +102,8 @@ impl<'a> MessageFormatter<'a> { message: runtime::MultiMessageHandle, args: &MessageArgs, ) -> Result { + #[cfg(feature = "profiling")] + profiling::function_scope!(); let mut out = String::new(); self.format_into(message, args, &mut out)?; Ok(out) @@ -133,6 +137,8 @@ impl<'a> MessageFormatter<'a> { message_id: &str, args: &MessageArgs, ) -> Result { + #[cfg(feature = "profiling")] + profiling::function_scope!(); let message = self.resolve(message_id)?; self.format(message, args) }