diff --git a/glean-core/glean-sym/src/lib.rs b/glean-core/glean-sym/src/lib.rs index 4986657461..c41439e210 100644 --- a/glean-core/glean-sym/src/lib.rs +++ b/glean-core/glean-sym/src/lib.rs @@ -108,6 +108,8 @@ pub mod metrics; pub mod types; mod util; +pub use types::PingType; + static GLEAN: LazyLock = LazyLock::new(|| metrics::GleanSym::load().unwrap()); // This boilerplate is usually generated by `uniffi::setup_scaffolding!()` diff --git a/glean-core/glean-sym/src/metrics.rs b/glean-core/glean-sym/src/metrics.rs index c5b083f6f8..946de45432 100644 --- a/glean-core/glean-sym/src/metrics.rs +++ b/glean-core/glean-sym/src/metrics.rs @@ -15,6 +15,118 @@ use crate::types::*; use crate::util::*; +#[derive(uniffi::Record)] +pub(crate) struct PingType { + handle: u64, +} +impl PingType { + unsafe fn clone_handle(&self) -> u64 { + unsafe { + let mut call_status = uniffi::RustCallStatus::default(); + (crate::GLEAN + .uniffi_glean_core_fn_clone_pingtype)(self.handle, &mut call_status) + } + } +} +impl PingType { + pub fn new( + name: String, + include_client_id: bool, + send_if_empty: bool, + precise_timestamps: bool, + include_info_sections: bool, + enabled: bool, + schedules_pings: Vec, + reason_codes: Vec, + follows_collection_enabled: bool, + uploader_capabilities: Vec, + ) -> Self { + unsafe { + let name = uniffi::FfiConverter::::lower(name); + let include_client_id = uniffi::FfiConverter::< + crate::UniFfiTag, + >::lower(include_client_id); + let send_if_empty = uniffi::FfiConverter::< + crate::UniFfiTag, + >::lower(send_if_empty); + let precise_timestamps = uniffi::FfiConverter::< + crate::UniFfiTag, + >::lower(precise_timestamps); + let include_info_sections = uniffi::FfiConverter::< + crate::UniFfiTag, + >::lower(include_info_sections); + let enabled = uniffi::FfiConverter::::lower(enabled); + let schedules_pings = uniffi::FfiConverter::< + crate::UniFfiTag, + >::lower(schedules_pings); + let reason_codes = uniffi::FfiConverter::< + crate::UniFfiTag, + >::lower(reason_codes); + let follows_collection_enabled = uniffi::FfiConverter::< + crate::UniFfiTag, + >::lower(follows_collection_enabled); + let uploader_capabilities = uniffi::FfiConverter::< + crate::UniFfiTag, + >::lower(uploader_capabilities); + let mut call_status = uniffi::RustCallStatus::default(); + let handle = (crate::GLEAN + .uniffi_glean_core_fn_constructor_pingtype_new)( + name.clone_for_ffi(), + include_client_id.clone_for_ffi(), + send_if_empty.clone_for_ffi(), + precise_timestamps.clone_for_ffi(), + include_info_sections.clone_for_ffi(), + enabled.clone_for_ffi(), + schedules_pings.clone_for_ffi(), + reason_codes.clone_for_ffi(), + follows_collection_enabled.clone_for_ffi(), + uploader_capabilities.clone_for_ffi(), + &mut call_status, + ); + uploader_capabilities.destroy(); + follows_collection_enabled.destroy(); + reason_codes.destroy(); + schedules_pings.destroy(); + enabled.destroy(); + include_info_sections.destroy(); + precise_timestamps.destroy(); + send_if_empty.destroy(); + include_client_id.destroy(); + name.destroy(); + Self { handle } + } + } + pub fn submit(&self, reason: Option) -> () { + unsafe { + let this = self.clone_handle(); + let reason = uniffi::FfiConverter::::lower(reason); + let mut call_status = uniffi::RustCallStatus::default(); + let res = (crate::GLEAN + .uniffi_glean_core_fn_method_pingtype_submit)( + this, + reason.clone_for_ffi(), + &mut call_status, + ); + reason.destroy(); + crate::util::LocalTryLift::try_lift(res).unwrap() + } + } + pub fn set_enabled(&self, enabled: bool) -> () { + unsafe { + let this = self.clone_handle(); + let enabled = uniffi::FfiConverter::::lower(enabled); + let mut call_status = uniffi::RustCallStatus::default(); + let res = (crate::GLEAN + .uniffi_glean_core_fn_method_pingtype_set_enabled)( + this, + enabled.clone_for_ffi(), + &mut call_status, + ); + enabled.destroy(); + crate::util::LocalTryLift::try_lift(res).unwrap() + } + } +} #[derive(uniffi::Record)] pub struct CounterMetric { handle: u64, @@ -1694,8 +1806,20 @@ library_binding! { & mut ::uniffi::RustCallStatus) -> ::uniffi::RustBuffer; fn ffi_glean_core_uniffi_contract_version() -> u32; fn ffi_glean_core_rustbuffer_free(bytes : ::uniffi::RustBuffer, call_status : & mut - ::uniffi::RustCallStatus); fn uniffi_glean_core_fn_clone_countermetric(handle : u64, + ::uniffi::RustCallStatus); fn uniffi_glean_core_fn_clone_pingtype(handle : u64, call_status : & mut ::uniffi::RustCallStatus) -> u64; fn + uniffi_glean_core_fn_constructor_pingtype_new(name : uniffi::RustBuffer, + include_client_id : i8, send_if_empty : i8, precise_timestamps : i8, + include_info_sections : i8, enabled : i8, schedules_pings : uniffi::RustBuffer, + reason_codes : uniffi::RustBuffer, follows_collection_enabled : i8, + uploader_capabilities : uniffi::RustBuffer, call_status : & mut + ::uniffi::RustCallStatus) -> u64; fn + uniffi_glean_core_fn_method_pingtype_submit(handle : u64, reason : + uniffi::RustBuffer, call_status : & mut ::uniffi::RustCallStatus) -> (); fn + uniffi_glean_core_fn_method_pingtype_set_enabled(handle : u64, enabled : i8, + call_status : & mut ::uniffi::RustCallStatus) -> (); fn + uniffi_glean_core_fn_clone_countermetric(handle : u64, call_status : & mut + ::uniffi::RustCallStatus) -> u64; fn uniffi_glean_core_fn_constructor_countermetric_new(meta : uniffi::RustBuffer, call_status : & mut ::uniffi::RustCallStatus) -> u64; fn uniffi_glean_core_fn_method_countermetric_add(handle : u64, amount : i32, call_status diff --git a/glean-core/glean-sym/src/types.rs b/glean-core/glean-sym/src/types.rs index 673d4f4d2a..2a57471777 100644 --- a/glean-core/glean-sym/src/types.rs +++ b/glean-core/glean-sym/src/types.rs @@ -206,3 +206,61 @@ impl EventMetric { self.inner.test_get_num_recorded_errors(error) } } + +pub struct PingType { + inner: crate::metrics::PingType, +} + +impl PingType { + /// Creates a new ping type. + /// + /// # Arguments + /// + /// * `name` - The name of the ping. + /// * `include_client_id` - Whether to include the client ID in the assembled ping when. + /// * `send_if_empty` - Whether the ping should be sent empty or not. + /// * `precise_timestamps` - Whether the ping should use precise timestamps for the start and end time. + /// * `include_info_sections` - Whether the ping should include the client/ping_info sections. + /// * `enabled` - Whether or not this ping is enabled. Note: Data that would be sent on a disabled + /// ping will still be collected and is discarded instead of being submitted. + /// * `schedules_pings` - A list of pings which are triggered for submission when this ping is + /// submitted. + /// * `reason_codes` - The valid reason codes for this ping. + /// * `uploader_capabilities` - The capabilities required during this ping's upload. + #[allow(clippy::too_many_arguments)] + pub fn new>( + name: A, + include_client_id: bool, + send_if_empty: bool, + precise_timestamps: bool, + include_info_sections: bool, + enabled: bool, + schedules_pings: Vec, + reason_codes: Vec, + follows_collection_enabled: bool, + uploader_capabilities: Vec, + ) -> Self { + let inner = crate::metrics::PingType::new( + name.into(), + include_client_id, + send_if_empty, + precise_timestamps, + include_info_sections, + enabled, + schedules_pings, + reason_codes, + follows_collection_enabled, + uploader_capabilities, + ); + + Self { inner } + } + + pub fn submit(&self, reason: Option<&str>) { + self.inner.submit(reason.map(|s| s.to_string())) + } + + pub fn set_enabled(&self, enabled: bool) { + self.inner.set_enabled(enabled) + } +} diff --git a/samples/glean-sym-test/app.py b/samples/glean-sym-test/app.py index f63b0e6b0b..f92f627627 100644 --- a/samples/glean-sym-test/app.py +++ b/samples/glean-sym-test/app.py @@ -14,6 +14,27 @@ def library_name(name): suffix = "dylib" if platform.system() == "Darwin" else "so" return f"lib{name}.{suffix}" +def check_ping_data(ping_type, sent_ping, amount): + data = open(sent_ping).read() + end_first_object = data.find("}") + payload = json.loads(data[end_first_object + 1 :]) + counter = payload["metrics"]["counter"] + events = payload["events"] + + if ping_type == "prototype": + assert 1 == counter["test.metrics.sample_counter"] + + assert amount == counter["dylib.counting"] + + assert 2 == len(events) + + no_extra = events[0] + assert "event" == no_extra["name"] + + with_extra = events[1] + assert "event_with_extras" == with_extra["name"] + extras = with_extra["extra"] + assert "true", extras["is_set"] def test_run(): xul = cdll.LoadLibrary(library_name("xul")) @@ -39,25 +60,13 @@ def test_run(): # * It contains several metrics with the expected values path = os.path.join(data_path, "sent_pings") for root, dirs, files in os.walk(path): - assert len(files) == 1 - assert "prototype-" in files[0] + assert len(files) == 2 + files = sorted(files) + assert "prototype-" in files[0] sent_ping = os.path.join(path, files[0]) - data = open(sent_ping).read() - end_first_object = data.find("}") - payload = json.loads(data[end_first_object + 1 :]) - counter = payload["metrics"]["counter"] - events = payload["events"] - - assert 1 == counter["test.metrics.sample_counter"] - assert amount == counter["dylib.counting"] - - assert 2 == len(events) - - no_extra = events[0] - assert "event" == no_extra["name"] + check_ping_data("prototype", sent_ping, amount) - with_extra = events[1] - assert "event_with_extras" == with_extra["name"] - extras = with_extra["extra"] - assert "true", extras["is_set"] + assert "services-info-" in files[1] + sent_ping = os.path.join(path, files[1]) + check_ping_data("services-info", sent_ping, amount) diff --git a/samples/glean-sym-test/services/build.rs b/samples/glean-sym-test/services/build.rs index f1b50e4680..e899ba7b06 100644 --- a/samples/glean-sym-test/services/build.rs +++ b/samples/glean-sym-test/services/build.rs @@ -3,6 +3,7 @@ use glean_build::Builder; fn main() { Builder::default() .file("metrics.yaml") + .file("pings.yaml") .format("rust_sym") .generate() .expect("Error generating Glean Rust bindings"); diff --git a/samples/glean-sym-test/services/lib.rs b/samples/glean-sym-test/services/lib.rs index f653b5058e..c8c14f66cb 100644 --- a/samples/glean-sym-test/services/lib.rs +++ b/samples/glean-sym-test/services/lib.rs @@ -16,6 +16,7 @@ pub mod glean_metrics { #[unsafe(no_mangle)] unsafe extern "C" fn record(amount: i32) { env_logger::init(); + let _ = &*glean_metrics::services_info; log::info!("Record invoked"); // A timer ID is passed through a `RustBuffer`, @@ -37,4 +38,6 @@ unsafe extern "C" fn record(amount: i32) { glean_metrics::dylib::event_with_extras.record(extra); glean_metrics::dylib::timing.stop_and_accumulate(tid); + + glean_metrics::services_info.submit(Some("recorded")); } diff --git a/samples/glean-sym-test/services/metrics.yaml b/samples/glean-sym-test/services/metrics.yaml index edaaea2fe7..85cde178c1 100644 --- a/samples/glean-sym-test/services/metrics.yaml +++ b/samples/glean-sym-test/services/metrics.yaml @@ -20,6 +20,7 @@ dylib: expires: never send_in_pings: - prototype + - services-info timing: type: timing_distribution @@ -34,6 +35,7 @@ dylib: expires: never send_in_pings: - prototype + - services-info data: type: string @@ -48,6 +50,7 @@ dylib: expires: never send_in_pings: - prototype + - services-info event: type: event @@ -62,6 +65,7 @@ dylib: expires: never send_in_pings: - prototype + - services-info event_with_extras: type: event @@ -76,6 +80,7 @@ dylib: expires: never send_in_pings: - prototype + - services-info extra_keys: is_set: type: boolean diff --git a/samples/glean-sym-test/services/pings.yaml b/samples/glean-sym-test/services/pings.yaml new file mode 100644 index 0000000000..1d5414e5c9 --- /dev/null +++ b/samples/glean-sym-test/services/pings.yaml @@ -0,0 +1,25 @@ +# 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/. + +# This file defines the pings that are recorded by the Glean SDK. +# Their code APIs is automatically generated, at build time using, +# the `glean_parser` PyPI package. + +--- +$schema: moz://mozilla.org/schemas/glean/pings/2-0-0 + +services-info: + description: | + A test ping sent from a Rust library. + include_client_id: true + send_if_empty: false + bugs: + - https://bugzilla.mozilla.org/123456789 + data_reviews: + - N/A + notification_emails: + - CHANGE-ME@example.com + reasons: + recorded: | + Recorded from Rust diff --git a/tools/glean-sym-parser/src/lib.rs b/tools/glean-sym-parser/src/lib.rs index 0ea90ce12e..f40c90b503 100755 --- a/tools/glean-sym-parser/src/lib.rs +++ b/tools/glean-sym-parser/src/lib.rs @@ -84,17 +84,16 @@ pub fn generate(content: &str) -> String { for elem in parsed { let Interface(iface) = elem else { continue }; let ident = iface.identifier; - if !ident.0.ends_with("Metric") { + if !ident.0.ends_with("Metric") && ident.0 != "PingType" { continue; } let structname = ident.0.to_lowercase().replace("_", ""); let ident = format_ident!("{}", ident.0); let extern_fn_ident = format_ident!("uniffi_glean_core_fn_clone_{}", structname); - let visibility = if structname == "eventmetric" { - quote! { pub(crate) } - } else { - quote! { pub } + let visibility = match &*structname { + "eventmetric" | "pingtype" => quote! { pub(crate) }, + _ => quote! { pub }, }; tokens.push(quote! { #[derive(uniffi::Record)]