summaryrefslogtreecommitdiffstats
path: root/third_party/rust/glean-core/src/metrics/ping.rs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /third_party/rust/glean-core/src/metrics/ping.rs
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/glean-core/src/metrics/ping.rs')
-rw-r--r--third_party/rust/glean-core/src/metrics/ping.rs210
1 files changed, 210 insertions, 0 deletions
diff --git a/third_party/rust/glean-core/src/metrics/ping.rs b/third_party/rust/glean-core/src/metrics/ping.rs
new file mode 100644
index 0000000000..dc37d76a45
--- /dev/null
+++ b/third_party/rust/glean-core/src/metrics/ping.rs
@@ -0,0 +1,210 @@
+// 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 std::fmt;
+use std::sync::Arc;
+
+use crate::ping::PingMaker;
+use crate::Glean;
+
+use uuid::Uuid;
+
+/// Stores information about a ping.
+///
+/// This is required so that given metric data queued on disk we can send
+/// pings with the correct settings, e.g. whether it has a client_id.
+#[derive(Clone)]
+pub struct PingType(Arc<InnerPing>);
+
+struct InnerPing {
+ /// The name of the ping.
+ pub name: String,
+ /// Whether the ping should include the client ID.
+ pub include_client_id: bool,
+ /// Whether the ping should be sent if it is empty
+ pub send_if_empty: bool,
+ /// Whether to use millisecond-precise start/end times.
+ pub precise_timestamps: bool,
+ /// The "reason" codes that this ping can send
+ pub reason_codes: Vec<String>,
+}
+
+impl fmt::Debug for PingType {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_struct("PingType")
+ .field("name", &self.0.name)
+ .field("include_client_id", &self.0.include_client_id)
+ .field("send_if_empty", &self.0.send_if_empty)
+ .field("precise_timestamps", &self.0.precise_timestamps)
+ .field("reason_codes", &self.0.reason_codes)
+ .finish()
+ }
+}
+
+// IMPORTANT:
+//
+// When changing this implementation, make sure all the operations are
+// also declared in the related trait in `../traits/`.
+impl PingType {
+ /// Creates a new ping type for the given name, whether to include the client ID and whether to
+ /// send this ping empty.
+ ///
+ /// # Arguments
+ ///
+ /// * `name` - The name of the ping.
+ /// * `include_client_id` - Whether to include the client ID in the assembled ping when submitting.
+ /// * `send_if_empty` - Whether the ping should be sent empty or not.
+ /// * `reason_codes` - The valid reason codes for this ping.
+ pub fn new<A: Into<String>>(
+ name: A,
+ include_client_id: bool,
+ send_if_empty: bool,
+ precise_timestamps: bool,
+ reason_codes: Vec<String>,
+ ) -> Self {
+ let this = Self(Arc::new(InnerPing {
+ name: name.into(),
+ include_client_id,
+ send_if_empty,
+ precise_timestamps,
+ reason_codes,
+ }));
+
+ // Register this ping.
+ // That will happen asynchronously and not block operation.
+ crate::register_ping_type(&this);
+
+ this
+ }
+
+ pub(crate) fn name(&self) -> &str {
+ &self.0.name
+ }
+
+ pub(crate) fn include_client_id(&self) -> bool {
+ self.0.include_client_id
+ }
+
+ pub(crate) fn send_if_empty(&self) -> bool {
+ self.0.send_if_empty
+ }
+
+ pub(crate) fn precise_timestamps(&self) -> bool {
+ self.0.precise_timestamps
+ }
+
+ /// Submits the ping for eventual uploading.
+ ///
+ /// The ping content is assembled as soon as possible, but upload is not
+ /// guaranteed to happen immediately, as that depends on the upload policies.
+ ///
+ /// If the ping currently contains no content, it will not be sent,
+ /// unless it is configured to be sent if empty.
+ ///
+ /// # Arguments
+ ///
+ /// * `reason` - the reason the ping was triggered. Included in the
+ /// `ping_info.reason` part of the payload.
+ pub fn submit(&self, reason: Option<String>) {
+ let ping = PingType(Arc::clone(&self.0));
+
+ // Need to separate access to the Glean object from access to global state.
+ // `trigger_upload` itself might lock the Glean object and we need to avoid that deadlock.
+ crate::dispatcher::launch(|| {
+ let sent =
+ crate::core::with_glean(move |glean| ping.submit_sync(glean, reason.as_deref()));
+ if sent {
+ let state = crate::global_state().lock().unwrap();
+ if let Err(e) = state.callbacks.trigger_upload() {
+ log::error!("Triggering upload failed. Error: {}", e);
+ }
+ }
+ })
+ }
+
+ /// Collects and submits a ping for eventual uploading.
+ ///
+ /// # Returns
+ ///
+ /// Whether the ping was succesfully assembled and queued.
+ #[doc(hidden)]
+ pub fn submit_sync(&self, glean: &Glean, reason: Option<&str>) -> bool {
+ if !glean.is_upload_enabled() {
+ log::info!("Glean disabled: not submitting any pings.");
+ return false;
+ }
+
+ let ping = &self.0;
+
+ // Allowing `clippy::manual_filter`.
+ // This causes a false positive.
+ // We have a side-effect in the `else` branch,
+ // so shouldn't delete it.
+ #[allow(unknown_lints)]
+ #[allow(clippy::manual_filter)]
+ let corrected_reason = match reason {
+ Some(reason) => {
+ if ping.reason_codes.contains(&reason.to_string()) {
+ Some(reason)
+ } else {
+ log::error!("Invalid reason code {} for ping {}", reason, ping.name);
+ None
+ }
+ }
+ None => None,
+ };
+
+ let ping_maker = PingMaker::new();
+ let doc_id = Uuid::new_v4().to_string();
+ let url_path = glean.make_path(&ping.name, &doc_id);
+ match ping_maker.collect(glean, self, corrected_reason, &doc_id, &url_path) {
+ None => {
+ log::info!(
+ "No content for ping '{}', therefore no ping queued.",
+ ping.name
+ );
+ false
+ }
+ Some(ping) => {
+ // This metric is recorded *after* the ping is collected (since
+ // that is the only way to know *if* it will be submitted). The
+ // implication of this is that the count for a metrics ping will
+ // be included in the *next* metrics ping.
+ glean
+ .additional_metrics
+ .pings_submitted
+ .get(ping.name)
+ .add_sync(glean, 1);
+
+ if let Err(e) = ping_maker.store_ping(glean.get_data_path(), &ping) {
+ log::warn!("IO error while writing ping to file: {}. Enqueuing upload of what we have in memory.", e);
+ glean.additional_metrics.io_errors.add_sync(glean, 1);
+ // `serde_json::to_string` only fails if serialization of the content
+ // fails or it contains maps with non-string keys.
+ // However `ping.content` is already a `JsonValue`,
+ // so both scenarios should be impossible.
+ let content =
+ ::serde_json::to_string(&ping.content).expect("ping serialization failed");
+ glean.upload_manager.enqueue_ping(
+ glean,
+ ping.doc_id,
+ ping.url_path,
+ &content,
+ Some(ping.headers),
+ );
+ return true;
+ }
+
+ glean.upload_manager.enqueue_ping_from_file(glean, &doc_id);
+
+ log::info!(
+ "The ping '{}' was submitted and will be sent as soon as possible",
+ ping.name
+ );
+
+ true
+ }
+ }
+ }
+}