Skip to main content

glean_core/core/
mod.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5use std::collections::HashMap;
6use std::fs::{self, File};
7use std::io::{self, Write};
8use std::path::{Path, PathBuf};
9use std::sync::atomic::{AtomicU8, Ordering};
10use std::sync::{Arc, Mutex};
11use std::time::Duration;
12
13use chrono::{DateTime, FixedOffset, SecondsFormat, Utc};
14use malloc_size_of_derive::MallocSizeOf;
15use once_cell::sync::OnceCell;
16use uuid::Uuid;
17
18#[cfg(feature = "sqlite")]
19use crate::database::sqlite::MigrationResult;
20use crate::database::Database;
21use crate::debug::DebugOptions;
22use crate::error::ClientIdFileError;
23use crate::event_database::EventDatabase;
24use crate::flows::FlowRegistry;
25use crate::internal_metrics::{
26    AdditionalMetrics, CoreMetrics, DatabaseMetrics, ExceptionState, HealthMetrics,
27};
28use crate::internal_pings::InternalPings;
29use crate::metrics::{
30    self, ExperimentMetric, Metric, MetricType, PingType, RecordedExperiment, RemoteSettingsConfig,
31};
32use crate::ping::PingMaker;
33use crate::session::{self, EventSessionContext, SessionManager, SessionMode, SessionState};
34use crate::storage::{StorageManager, INTERNAL_STORAGE};
35use crate::upload::{PingUploadManager, PingUploadTask, UploadResult, UploadTaskAction};
36#[cfg(feature = "sqlite")]
37use crate::util::truncate_string_at_boundary;
38use crate::util::{local_now_with_offset, sanitize_application_id};
39use crate::{
40    scheduler, system, AttributionMetrics, CommonMetricData, DistributionMetrics, ErrorKind,
41    InternalConfiguration, Lifetime, PingRateLimit, Result, DEFAULT_MAX_EVENTS,
42    GLEAN_SCHEMA_VERSION, GLEAN_VERSION, KNOWN_CLIENT_ID,
43};
44
45const CLIENT_ID_PLAIN_FILENAME: &str = "client_id.txt";
46static GLEAN: OnceCell<Mutex<Glean>> = OnceCell::new();
47
48/// Rate limiting defaults
49/// 15 pings every 60 seconds.
50pub const DEFAULT_SECONDS_PER_INTERVAL: u64 = 60;
51pub const DEFAULT_PINGS_PER_INTERVAL: u32 = 15;
52
53pub fn global_glean() -> Option<&'static Mutex<Glean>> {
54    GLEAN.get()
55}
56
57/// Sets or replaces the global Glean object.
58pub fn setup_glean(glean: Glean) -> Result<()> {
59    // The `OnceCell` type wrapping our Glean is thread-safe and can only be set once.
60    // Therefore even if our check for it being empty succeeds, setting it could fail if a
61    // concurrent thread is quicker in setting it.
62    // However this will not cause a bigger problem, as the second `set` operation will just fail.
63    // We can log it and move on.
64    //
65    // For all wrappers this is not a problem, as the Glean object is intialized exactly once on
66    // calling `initialize` on the global singleton and further operations check that it has been
67    // initialized.
68    if GLEAN.get().is_none() {
69        if GLEAN.set(Mutex::new(glean)).is_err() {
70            log::warn!(
71                "Global Glean object is initialized already. This probably happened concurrently."
72            )
73        }
74    } else {
75        // We allow overriding the global Glean object to support test mode.
76        // In test mode the Glean object is fully destroyed and recreated.
77        // This all happens behind a mutex and is therefore also thread-safe..
78        let mut lock = GLEAN.get().unwrap().lock().unwrap();
79        *lock = glean;
80    }
81    Ok(())
82}
83
84/// Execute `f` passing the global Glean object.
85///
86/// Panics if the global Glean object has not been set.
87pub fn with_glean<F, R>(f: F) -> R
88where
89    F: FnOnce(&Glean) -> R,
90{
91    let glean = global_glean().expect("Global Glean object not initialized");
92    let lock = glean.lock().unwrap();
93    f(&lock)
94}
95
96/// Execute `f` passing the global Glean object mutable.
97///
98/// Panics if the global Glean object has not been set.
99pub fn with_glean_mut<F, R>(f: F) -> R
100where
101    F: FnOnce(&mut Glean) -> R,
102{
103    let glean = global_glean().expect("Global Glean object not initialized");
104    let mut lock = glean.lock().unwrap();
105    f(&mut lock)
106}
107
108/// Execute `f` passing the global Glean object if it has been set.
109///
110/// Returns `None` if the global Glean object has not been set.
111/// Returns `Some(T)` otherwise.
112pub fn with_opt_glean<F, R>(f: F) -> Option<R>
113where
114    F: FnOnce(&Glean) -> R,
115{
116    let glean = global_glean()?;
117    let lock = glean.lock().unwrap();
118    Some(f(&lock))
119}
120
121/// The object holding meta information about a Glean instance.
122///
123/// ## Example
124///
125/// Create a new Glean instance, register a ping, record a simple counter and then send the final
126/// ping.
127///
128/// ```rust,no_run
129/// # use glean_core::{Glean, InternalConfiguration, CommonMetricData, metrics::*};
130/// let cfg = InternalConfiguration {
131///     data_path: "/tmp/glean".into(),
132///     application_id: "glean.sample.app".into(),
133///     language_binding_name: "Rust".into(),
134///     upload_enabled: true,
135///     max_events: None,
136///     delay_ping_lifetime_io: false,
137///     app_build: "".into(),
138///     use_core_mps: false,
139///     trim_data_to_registered_pings: false,
140///     log_level: None,
141///     rate_limit: None,
142///     enable_event_timestamps: true,
143///     experimentation_id: None,
144///     enable_internal_pings: true,
145///     ping_schedule: Default::default(),
146///     ping_lifetime_threshold: 1000,
147///     ping_lifetime_max_time: 2000,
148///     max_pending_pings_count: None,
149///     max_pending_pings_directory_size: None,
150///     session_mode: glean_core::SessionMode::Auto,
151///     session_sample_rate: 1.0,
152///     session_inactivity_timeout_ms: 1_800_000,
153///     events_ping_acceleration_factor: None,
154///     enable_store_submitted_pings: false,
155///     flows: vec![],
156/// };
157/// let mut glean = Glean::new(cfg).unwrap();
158/// let ping = PingType::new("sample", true, false, true, true, true, vec![], vec![], true, vec![]);
159/// glean.register_ping_type(&ping);
160///
161/// let call_counter: CounterMetric = CounterMetric::new(CommonMetricData {
162///     name: "calls".into(),
163///     category: "local".into(),
164///     send_in_pings: vec!["sample".into()],
165///     ..Default::default()
166/// });
167///
168/// call_counter.add_sync(&glean, 1);
169///
170/// ping.submit_sync(&glean, None);
171/// ```
172///
173/// ## Note
174///
175/// In specific language bindings, this is usually wrapped in a singleton and all metric recording goes to a single instance of this object.
176/// In the Rust core, it is possible to create multiple instances, which is used in testing.
177#[derive(Debug, MallocSizeOf)]
178pub struct Glean {
179    upload_enabled: bool,
180    pub(crate) data_store: Option<Database>,
181    event_data_store: EventDatabase,
182    pub(crate) core_metrics: CoreMetrics,
183    pub(crate) additional_metrics: AdditionalMetrics,
184    pub(crate) database_metrics: DatabaseMetrics,
185    pub(crate) health_metrics: HealthMetrics,
186    pub(crate) internal_pings: InternalPings,
187    data_path: PathBuf,
188    application_id: String,
189    ping_registry: HashMap<String, PingType>,
190    #[ignore_malloc_size_of = "external non-allocating type"]
191    start_time: DateTime<FixedOffset>,
192    max_events: u32,
193    is_first_run: bool,
194    pub(crate) upload_manager: PingUploadManager,
195    debug: DebugOptions,
196    pub(crate) app_build: String,
197    pub(crate) schedule_metrics_pings: bool,
198    pub(crate) remote_settings_epoch: AtomicU8,
199    #[ignore_malloc_size_of = "TODO: Expose Glean's inner memory allocations (bug 1960592)"]
200    pub(crate) remote_settings_config: Arc<Mutex<RemoteSettingsConfig>>,
201    pub(crate) with_timestamps: bool,
202    pub(crate) ping_schedule: HashMap<String, Vec<String>>,
203    pub(crate) flow_registry: FlowRegistry,
204    #[ignore_malloc_size_of = "TODO: Expose session memory allocations (bug 2043355)"]
205    pub(crate) session_manager: SessionManager,
206    events_ping_acceleration_factor: Option<usize>,
207    pub(crate) store_submitted_pings_enabled: bool,
208}
209
210impl Glean {
211    /// Creates and initializes a new Glean object for use in a subprocess.
212    ///
213    /// Importantly, this will not send any pings at startup, since that
214    /// sort of management should only happen in the main process.
215    pub fn new_for_subprocess(cfg: &InternalConfiguration, scan_directories: bool) -> Result<Self> {
216        log::info!("Creating new Glean v{}", GLEAN_VERSION);
217
218        let application_id = sanitize_application_id(&cfg.application_id);
219        if application_id.is_empty() {
220            return Err(ErrorKind::InvalidConfig.into());
221        }
222
223        let data_path = Path::new(&cfg.data_path);
224        let event_data_store = EventDatabase::new(data_path)?;
225
226        // Create an upload manager with rate limiting of 15 pings every 60 seconds.
227        let mut upload_manager = PingUploadManager::new(&cfg.data_path, &cfg.language_binding_name);
228        let rate_limit = cfg.rate_limit.as_ref().unwrap_or(&PingRateLimit {
229            seconds_per_interval: DEFAULT_SECONDS_PER_INTERVAL,
230            pings_per_interval: DEFAULT_PINGS_PER_INTERVAL,
231        });
232        upload_manager.set_rate_limiter(
233            rate_limit.seconds_per_interval,
234            rate_limit.pings_per_interval,
235        );
236        if let Some(n) = cfg.max_pending_pings_count {
237            upload_manager.set_max_pending_pings_count(n);
238        }
239        if let Some(n) = cfg.max_pending_pings_directory_size {
240            upload_manager.set_max_pending_pings_directory_size(n);
241        }
242
243        // We only scan the pending ping directories when calling this from a subprocess,
244        // when calling this from ::new we need to scan the directories after dealing with the upload state.
245        if scan_directories {
246            let _scanning_thread = upload_manager.scan_pending_pings_directories(false);
247        }
248
249        let start_time = local_now_with_offset();
250        let mut this = Self {
251            upload_enabled: cfg.upload_enabled,
252            // In the subprocess, we want to avoid accessing the database entirely.
253            // The easiest way to ensure that is to just not initialize it.
254            data_store: None,
255            event_data_store,
256            core_metrics: CoreMetrics::new(),
257            additional_metrics: AdditionalMetrics::new(),
258            database_metrics: DatabaseMetrics::new(),
259            health_metrics: HealthMetrics::new(),
260            internal_pings: InternalPings::new(cfg.enable_internal_pings),
261            upload_manager,
262            data_path: PathBuf::from(&cfg.data_path),
263            application_id,
264            ping_registry: HashMap::new(),
265            start_time,
266            max_events: cfg.max_events.unwrap_or(DEFAULT_MAX_EVENTS),
267            is_first_run: false,
268            debug: DebugOptions::new(),
269            app_build: cfg.app_build.to_string(),
270            // Subprocess doesn't use "metrics" pings so has no need for a scheduler.
271            schedule_metrics_pings: false,
272            remote_settings_epoch: AtomicU8::new(0),
273            remote_settings_config: Arc::new(Mutex::new(RemoteSettingsConfig::new())),
274            with_timestamps: cfg.enable_event_timestamps,
275            ping_schedule: cfg.ping_schedule.clone(),
276            // Built here, rather than applied after init like pre-init ping
277            // registration, because `Glean::new()` restores persisted session
278            // state (and with it, flow instances) before it returns.
279            flow_registry: FlowRegistry::new(&cfg.flows),
280            // The SessionManager is deliberately left in its default (hollow)
281            // state for subprocesses. `restore_session_state_from_storage()`
282            // is only called in `Glean::new()`, not here, so the subprocess
283            // never loads or mutates the main process's persisted session
284            // state. This prevents subprocesses from interfering with the
285            // main process's session lifecycle (seq counters, dirty flags,
286            // boundary events, etc.).
287            session_manager: SessionManager::new(
288                cfg.session_mode,
289                cfg.session_sample_rate,
290                std::time::Duration::from_millis(cfg.session_inactivity_timeout_ms),
291            ),
292            events_ping_acceleration_factor: cfg
293                .events_ping_acceleration_factor
294                .map(|x| x as usize),
295            store_submitted_pings_enabled: cfg.enable_store_submitted_pings,
296        };
297
298        // Ensuring these pings are registered.
299        let pings = this.internal_pings.clone();
300        this.register_ping_type(&pings.baseline);
301        this.register_ping_type(&pings.metrics);
302        this.register_ping_type(&pings.events);
303        this.register_ping_type(&pings.health);
304        this.register_ping_type(&pings.deletion_request);
305
306        Ok(this)
307    }
308
309    /// Creates and initializes a new Glean object.
310    ///
311    /// This will create the necessary directories and files in
312    /// [`cfg.data_path`](InternalConfiguration::data_path). This will also initialize
313    /// the core metrics.
314    pub fn new(cfg: InternalConfiguration) -> Result<Self> {
315        let mut glean = Self::new_for_subprocess(&cfg, false)?;
316
317        // Creating the data store creates the necessary path as well.
318        // If that fails we bail out and don't initialize further.
319        let data_path = Path::new(&cfg.data_path);
320        let ping_lifetime_threshold = cfg.ping_lifetime_threshold as usize;
321        let ping_lifetime_max_time = Duration::from_millis(cfg.ping_lifetime_max_time);
322        glean.data_store = Some(Database::new(
323            data_path,
324            cfg.delay_ping_lifetime_io,
325            ping_lifetime_threshold,
326            ping_lifetime_max_time,
327        )?);
328
329        #[cfg(feature = "sqlite")]
330        if let Some(state) = glean.data_store.as_mut().unwrap().migration_state.take() {
331            glean
332                .database_metrics
333                .migrated_metrics
334                .add_sync(&glean, state.migrated_metrics);
335            glean
336                .database_metrics
337                .metrics_in_sqlite
338                .add_sync(&glean, state.metrics_in_sql);
339            glean
340                .database_metrics
341                .failed_metrics
342                .add_sync(&glean, state.failed_metrics);
343
344            let duration_ns = state.duration.as_nanos().try_into().unwrap_or(u64::MAX);
345            glean
346                .database_metrics
347                .migration_duration
348                .accumulate_raw_samples_nanos_sync(&glean, &[duration_ns]);
349        }
350
351        #[cfg(feature = "sqlite")]
352        if glean.data_store.as_mut().unwrap().migration_error == MigrationResult::Error {
353            glean.database_metrics.migration_error.add_sync(&glean, 1);
354        }
355
356        glean.restore_session_state_from_storage();
357
358        // This code references different states from the "Client ID recovery" flowchart.
359        // See https://mozilla.github.io/glean/dev/core/internal/client_id_recovery.html for details.
360
361        // We don't have the database yet when we first encounter the error,
362        // so we store it and apply it later.
363        // state (a)
364        let stored_client_id = match glean.client_id_from_file() {
365            Ok(id) if id == *KNOWN_CLIENT_ID => {
366                glean
367                    .health_metrics
368                    .file_read_error
369                    .get("c0ffee-in-file")
370                    .add_sync(&glean, 1);
371                None
372            }
373            Ok(id) => Some(id),
374            Err(ClientIdFileError::NotFound) => {
375                // That's ok, the file might just not exist yet.
376                glean
377                    .health_metrics
378                    .file_read_error
379                    .get("file-not-found")
380                    .add_sync(&glean, 1);
381                None
382            }
383            Err(ClientIdFileError::PermissionDenied) => {
384                // state (b)
385                // Uhm ... who removed our permission?
386                glean
387                    .health_metrics
388                    .file_read_error
389                    .get("permission-denied")
390                    .add_sync(&glean, 1);
391                None
392            }
393            Err(ClientIdFileError::ParseError(e)) => {
394                // state (b)
395                log::trace!("reading cliend_id.txt. Could not parse into UUID: {e}");
396                glean
397                    .health_metrics
398                    .file_read_error
399                    .get("parse")
400                    .add_sync(&glean, 1);
401                None
402            }
403            Err(ClientIdFileError::IoError(e)) => {
404                // state (b)
405                // We can't handle other IO errors (most couldn't occur on this operation anyway)
406                log::trace!("reading client_id.txt. Unexpected io error: {e}");
407                glean
408                    .health_metrics
409                    .file_read_error
410                    .get("io")
411                    .add_sync(&glean, 1);
412                None
413            }
414        };
415
416        {
417            let data_store = glean.data_store.as_ref().unwrap();
418            let file_size = data_store.file_size().map(|n| n.get()).unwrap_or(0);
419
420            // If we have a client ID on disk, we check the database
421            if let Some(stored_client_id) = stored_client_id {
422                // state (c)
423                if file_size == 0 {
424                    log::trace!("no database. database size={file_size}. stored_client_id={stored_client_id}");
425                    // state (d)
426                    glean
427                        .health_metrics
428                        .recovered_client_id
429                        .set_from_uuid_sync(&glean, stored_client_id);
430                    glean
431                        .health_metrics
432                        .exception_state
433                        .set_sync(&glean, ExceptionState::EmptyDb);
434
435                    // state (e) -- mitigation: store recovered client ID in DB
436                    glean
437                        .core_metrics
438                        .client_id
439                        .set_from_uuid_sync(&glean, stored_client_id);
440                } else {
441                    let db_client_id = glean
442                        .core_metrics
443                        .client_id
444                        .get_value(&glean, Some("glean_client_info"));
445
446                    match db_client_id {
447                        None => {
448                            // state (f)
449                            log::trace!("no client_id in DB. stored_client_id={stored_client_id}");
450                            glean
451                                .health_metrics
452                                .exception_state
453                                .set_sync(&glean, ExceptionState::RegenDb);
454
455                            // state (e) -- mitigation: store recovered client ID in DB
456                            glean
457                                .core_metrics
458                                .client_id
459                                .set_from_uuid_sync(&glean, stored_client_id);
460                        }
461                        Some(db_client_id) if db_client_id == *KNOWN_CLIENT_ID => {
462                            // state (i)
463                            log::trace!(
464                                "c0ffee client_id in DB, stored_client_id={stored_client_id}"
465                            );
466                            glean
467                                .health_metrics
468                                .recovered_client_id
469                                .set_from_uuid_sync(&glean, stored_client_id);
470                            glean
471                                .health_metrics
472                                .exception_state
473                                .set_sync(&glean, ExceptionState::C0ffeeInDb);
474
475                            // If we have a recovered client ID we also overwrite the database.
476                            // state (e)
477                            glean
478                                .core_metrics
479                                .client_id
480                                .set_from_uuid_sync(&glean, stored_client_id);
481                        }
482                        Some(db_client_id) if db_client_id == stored_client_id => {
483                            // all valid. nothing to do
484                            log::trace!("database consistent. db_client_id == stored_client_id: {db_client_id}");
485                        }
486                        Some(db_client_id) => {
487                            // state (g)
488                            log::trace!(
489                                "client_id mismatch. db_client_id{db_client_id}, stored_client_id={stored_client_id}. Overwriting file with db's client_id."
490                            );
491                            glean
492                                .health_metrics
493                                .recovered_client_id
494                                .set_from_uuid_sync(&glean, stored_client_id);
495                            glean
496                                .health_metrics
497                                .exception_state
498                                .set_sync(&glean, ExceptionState::ClientIdMismatch);
499
500                            // state (h)
501                            glean.store_client_id_with_reporting(
502                                db_client_id,
503                                "client_id mismatch will re-occur.",
504                            );
505                        }
506                    }
507                }
508            } else {
509                log::trace!("No stored client ID. Database might have it.");
510
511                let db_client_id = glean
512                    .core_metrics
513                    .client_id
514                    .get_value(&glean, Some("glean_client_info"));
515                if let Some(db_client_id) = db_client_id {
516                    // state (h)
517                    glean.store_client_id_with_reporting(
518                        db_client_id,
519                        "Might happen on next init then.",
520                    );
521                } else {
522                    log::trace!("Database has no client ID either. We might be fresh!");
523                }
524            }
525        }
526
527        // Set experimentation identifier (if any)
528        if let Some(experimentation_id) = &cfg.experimentation_id {
529            glean
530                .additional_metrics
531                .experimentation_id
532                .set_sync(&glean, experimentation_id.to_string());
533        }
534
535        glean.set_early_metrics();
536
537        // The upload enabled flag may have changed since the last run, for
538        // example by the changing of a config file.
539        if cfg.upload_enabled {
540            // If upload is enabled, just follow the normal code path to
541            // instantiate the core metrics.
542            glean.on_upload_enabled();
543        } else {
544            // If upload is disabled, then clear the metrics
545            // but do not send a deletion request ping.
546            // If we have run before, and we have an old client_id,
547            // do the full upload disabled operations to clear metrics
548            // and send a deletion request ping.
549            match glean
550                .core_metrics
551                .client_id
552                .get_value(&glean, Some("glean_client_info"))
553            {
554                None => glean.clear_metrics(),
555                Some(uuid) => {
556                    if let Err(e) = glean.remove_stored_client_id() {
557                        log::error!("Couldn't remove client ID on disk. This might lead to a resurrection of this client ID later. Error: {e}");
558                    }
559                    if uuid == *KNOWN_CLIENT_ID {
560                        // Previously Glean kept the KNOWN_CLIENT_ID stored.
561                        // Let's ensure we erase it now.
562                        if let Some(data) = glean.data_store.as_ref() {
563                            _ = data.remove_single_metric(
564                                Lifetime::User,
565                                "glean_client_info",
566                                "client_id",
567                            );
568                        }
569                    } else {
570                        // Temporarily enable uploading so we can submit a
571                        // deletion request ping.
572                        glean.upload_enabled = true;
573                        glean.on_upload_disabled(true);
574                    }
575                }
576            }
577        }
578
579        // We set this only for non-subprocess situations.
580        // If internal pings are disabled, we don't set up the MPS either,
581        // it wouldn't send any data anyway.
582        glean.schedule_metrics_pings = cfg.enable_internal_pings && cfg.use_core_mps;
583
584        // We only scan the pendings pings directories **after** dealing with the upload state.
585        // If upload is disabled, we delete all pending pings files
586        // and we need to do that **before** scanning the pending pings folder
587        // to ensure we don't enqueue pings before their files are deleted.
588        let _scanning_thread = glean.upload_manager.scan_pending_pings_directories(true);
589
590        Ok(glean)
591    }
592
593    /// For tests make it easy to create a Glean object using only the required configuration.
594    #[cfg(test)]
595    pub(crate) fn with_options(
596        data_path: &str,
597        application_id: &str,
598        upload_enabled: bool,
599        enable_internal_pings: bool,
600    ) -> Self {
601        let cfg = InternalConfiguration {
602            data_path: data_path.into(),
603            application_id: application_id.into(),
604            language_binding_name: "Rust".into(),
605            upload_enabled,
606            max_events: None,
607            delay_ping_lifetime_io: false,
608            app_build: "Unknown".into(),
609            use_core_mps: false,
610            trim_data_to_registered_pings: false,
611            log_level: None,
612            rate_limit: None,
613            enable_event_timestamps: true,
614            experimentation_id: None,
615            enable_internal_pings,
616            ping_schedule: Default::default(),
617            ping_lifetime_threshold: 0,
618            ping_lifetime_max_time: 0,
619            max_pending_pings_count: None,
620            max_pending_pings_directory_size: None,
621            session_mode: SessionMode::Auto,
622            session_sample_rate: 1.0,
623            session_inactivity_timeout_ms: 1_800_000,
624            events_ping_acceleration_factor: None,
625            enable_store_submitted_pings: false,
626            flows: vec![],
627        };
628
629        let mut glean = Self::new(cfg).unwrap();
630
631        // Disable all upload manager policies for testing
632        glean.upload_manager = PingUploadManager::no_policy(data_path);
633
634        glean
635    }
636
637    /// Close the database connection.
638    ///
639    /// After this Glean needs to be reinitialized.
640    pub fn close_db(&mut self) {
641        self.data_store = None;
642    }
643
644    fn client_id_file_path(&self) -> PathBuf {
645        self.data_path.join(CLIENT_ID_PLAIN_FILENAME)
646    }
647
648    /// Write the client ID to a separate plain file on disk
649    ///
650    /// Use `store_client_id_with_reporting` to handle the error cases.
651    fn store_client_id(&self, client_id: Uuid) -> Result<(), ClientIdFileError> {
652        let mut fp = File::create(self.client_id_file_path())?;
653
654        let mut buffer = Uuid::encode_buffer();
655        let uuid_str = client_id.hyphenated().encode_lower(&mut buffer);
656        fp.write_all(uuid_str.as_bytes())?;
657        fp.sync_all()?;
658
659        Ok(())
660    }
661
662    /// Write the client ID to a separate plain file on disk
663    ///
664    /// When an error occurs an error message is logged and the error is counted in a metric.
665    fn store_client_id_with_reporting(&self, client_id: Uuid, msg: &str) {
666        if let Err(err) = self.store_client_id(client_id) {
667            log::error!(
668                "Could not write {client_id} to state file. {} Error: {err}",
669                msg
670            );
671            match err {
672                ClientIdFileError::NotFound => {
673                    self.health_metrics
674                        .file_write_error
675                        .get("not-found")
676                        .add_sync(self, 1);
677                }
678                ClientIdFileError::PermissionDenied => {
679                    self.health_metrics
680                        .file_write_error
681                        .get("permission-denied")
682                        .add_sync(self, 1);
683                }
684                ClientIdFileError::IoError(..) => {
685                    self.health_metrics
686                        .file_write_error
687                        .get("io")
688                        .add_sync(self, 1);
689                }
690                ClientIdFileError::ParseError(..) => {
691                    log::error!("Parse error encountered on file write. This is impossible.");
692                }
693            }
694        }
695    }
696
697    /// Try to load a client ID from the plain file on disk.
698    fn client_id_from_file(&self) -> Result<Uuid, ClientIdFileError> {
699        let uuid_str = fs::read_to_string(self.client_id_file_path())?;
700        // We don't write a newline, but we still trim it. Who knows who else touches that file by accident.
701        // We're also a bit more lenient in what we accept here:
702        // uppercase, lowercase, with or without dashes, urn, braced (and whatever else `Uuid`
703        // parses by default).
704        let uuid = Uuid::try_parse(uuid_str.trim_end())?;
705        Ok(uuid)
706    }
707
708    /// Remove the stored client ID from disk.
709    /// Should only be called when the client ID is also removed from the database.
710    fn remove_stored_client_id(&self) -> Result<(), ClientIdFileError> {
711        match fs::remove_file(self.client_id_file_path()) {
712            Ok(()) => Ok(()),
713            Err(e) if e.kind() == io::ErrorKind::NotFound => {
714                // File was already missing. No need to report that.
715                Ok(())
716            }
717            Err(e) => Err(e.into()),
718        }
719    }
720
721    /// Initializes the core metrics managed by Glean's Rust core.
722    fn initialize_core_metrics(&mut self) {
723        let need_new_client_id = match self
724            .core_metrics
725            .client_id
726            .get_value(self, Some("glean_client_info"))
727        {
728            None => true,
729            Some(uuid) => uuid == *KNOWN_CLIENT_ID,
730        };
731        if need_new_client_id {
732            let new_clientid = self.core_metrics.client_id.generate_and_set_sync(self);
733            self.store_client_id_with_reporting(new_clientid, "New client in database only.");
734        }
735    }
736
737    /// Initializes the database metrics managed by Glean's Rust core.
738    fn initialize_database_metrics(&mut self) {
739        log::trace!("Initializing database metrics");
740
741        if let Some(size) = self
742            .data_store
743            .as_ref()
744            .and_then(|database| database.file_size())
745        {
746            log::trace!("Database file size: {}", size.get());
747            self.database_metrics
748                .size
749                .accumulate_sync(self, size.get() as i64)
750        }
751
752        #[cfg(feature = "sqlite")]
753        if let Some(load_state) = self
754            .data_store
755            .as_ref()
756            .and_then(|database| database.load_state())
757        {
758            use crate::metrics::string::MAX_LENGTH_VALUE;
759            let load_state = truncate_string_at_boundary(load_state, MAX_LENGTH_VALUE);
760            self.database_metrics.load_error.set_sync(self, load_state)
761        }
762
763        #[cfg(not(feature = "sqlite"))]
764        if let Some(rkv_load_state) = self
765            .data_store
766            .as_ref()
767            .and_then(|database| database.rkv_load_state())
768        {
769            self.database_metrics
770                .rkv_load_error
771                .set_sync(self, rkv_load_state);
772        }
773    }
774
775    /// Signals that the environment is ready to submit pings.
776    ///
777    /// Should be called when Glean is initialized to the point where it can correctly assemble pings.
778    /// Usually called from the language binding after all of the core metrics have been set
779    /// and the ping types have been registered.
780    ///
781    /// # Arguments
782    ///
783    /// * `trim_data_to_registered_pings` - Whether we should limit to storing data only for
784    ///   data belonging to pings previously registered via `register_ping_type`.
785    ///
786    /// # Returns
787    ///
788    /// Whether the "events" ping was submitted.
789    pub fn on_ready_to_submit_pings(&mut self, trim_data_to_registered_pings: bool) -> bool {
790        // When upload is disabled on init we already clear out metrics.
791        // However at that point not all pings are registered and so we keep that data around.
792        // By the time we would be ready to submit we try again cleaning out metrics from
793        // now-known pings.
794        if !self.upload_enabled {
795            log::debug!("on_ready_to_submit_pings. let's clear pings once again.");
796            self.clear_metrics();
797        }
798
799        self.event_data_store
800            .flush_pending_events_on_startup(self, trim_data_to_registered_pings)
801    }
802
803    /// Sets whether upload is enabled or not.
804    ///
805    /// When uploading is disabled, metrics aren't recorded at all and no
806    /// data is uploaded.
807    ///
808    /// When disabling, all pending metrics, events and queued pings are cleared.
809    ///
810    /// When enabling, the core Glean metrics are recreated.
811    ///
812    /// If the value of this flag is not actually changed, this is a no-op.
813    ///
814    /// # Arguments
815    ///
816    /// * `flag` - When true, enable metric collection.
817    ///
818    /// # Returns
819    ///
820    /// Whether the flag was different from the current value,
821    /// and actual work was done to clear or reinstate metrics.
822    pub fn set_upload_enabled(&mut self, flag: bool) -> bool {
823        log::info!("Upload enabled: {:?}", flag);
824
825        if self.upload_enabled != flag {
826            if flag {
827                self.on_upload_enabled();
828            } else {
829                self.on_upload_disabled(false);
830            }
831            true
832        } else {
833            false
834        }
835    }
836
837    /// Sets whether storing submitted pings is enabled or not.
838    ///
839    /// # Arguments
840    ///
841    /// * `enabled` - When true, enables storing submitted pings.
842    ///
843    pub fn set_store_submitted_pings_enabled(&mut self, enabled: bool) {
844        self.store_submitted_pings_enabled = enabled;
845    }
846
847    /// Enable or disable a ping.
848    ///
849    /// Disabling a ping causes all data for that ping to be removed from storage
850    /// and all pending pings of that type to be deleted.
851    ///
852    /// **Note**: Do not use directly. Call `PingType::set_enabled` instead.
853    #[doc(hidden)]
854    pub fn set_ping_enabled(&mut self, ping: &PingType, enabled: bool) {
855        ping.store_enabled(enabled);
856        if !enabled {
857            if let Some(data) = self.data_store.as_ref() {
858                _ = data.clear_ping_lifetime_storage(ping.name());
859                _ = data.clear_lifetime_storage(Lifetime::User, ping.name());
860                _ = data.clear_lifetime_storage(Lifetime::Application, ping.name());
861            }
862            if let Err(err) = self.event_storage().clear_for_ping(ping.name()) {
863                log::warn!("Error clearing event storage for ping: {}", err);
864            }
865
866            let ping_maker = PingMaker::new();
867            let disabled_pings = &[ping.name()][..];
868            if let Err(err) = ping_maker.clear_pending_pings(self.get_data_path(), disabled_pings) {
869                log::warn!("Error clearing pending pings: {}", err);
870            }
871        }
872    }
873
874    /// Determines whether upload is enabled.
875    ///
876    /// When upload is disabled, no data will be recorded.
877    pub fn is_upload_enabled(&self) -> bool {
878        self.upload_enabled
879    }
880
881    /// Check if a ping is enabled.
882    ///
883    /// Note that some internal "ping" names are considered to be always enabled.
884    ///
885    /// If a ping is not known to Glean ("unregistered") it is always considered disabled.
886    /// If a ping is known, it can be enabled/disabled at any point.
887    /// Only data for enabled pings is recorded.
888    /// Disabled pings are never submitted.
889    pub fn is_ping_enabled(&self, ping: &str) -> bool {
890        // We "abuse" pings/storage names for internal data.
891        const DEFAULT_ENABLED: &[&str] = &[
892            "glean_client_info",
893            "glean_internal_info",
894            // for `experimentation_id`.
895            // That should probably have gone into `glean_internal_info` instead.
896            "all-pings",
897        ];
898
899        // `client_info`-like stuff is always enabled.
900        if DEFAULT_ENABLED.contains(&ping) {
901            return true;
902        }
903
904        let Some(ping) = self.ping_registry.get(ping) else {
905            log::trace!("Unknown ping {ping}. Assuming disabled.");
906            return false;
907        };
908
909        ping.enabled(self)
910    }
911
912    /// Handles the changing of state from upload disabled to enabled.
913    ///
914    /// Should only be called when the state actually changes.
915    ///
916    /// The `upload_enabled` flag is set to true and the core Glean metrics are
917    /// recreated.
918    fn on_upload_enabled(&mut self) {
919        self.upload_enabled = true;
920        self.initialize_core_metrics();
921        self.initialize_database_metrics();
922    }
923
924    /// Handles the changing of state from upload enabled to disabled.
925    ///
926    /// Should only be called when the state actually changes.
927    ///
928    /// A deletion_request ping is sent, all pending metrics, events and queued
929    /// pings are cleared, and the client_id is set to KNOWN_CLIENT_ID.
930    /// Afterward, the upload_enabled flag is set to false.
931    fn on_upload_disabled(&mut self, during_init: bool) {
932        // The upload_enabled flag should be true here, or the deletion ping
933        // won't be submitted.
934        let reason = if during_init {
935            Some("at_init")
936        } else {
937            Some("set_upload_enabled")
938        };
939        if !self
940            .internal_pings
941            .deletion_request
942            .submit_sync(self, reason)
943        {
944            log::error!("Failed to submit deletion-request ping on optout.");
945        }
946        self.clear_metrics();
947        self.upload_enabled = false;
948    }
949
950    /// Clear any pending metrics when telemetry is disabled.
951    fn clear_metrics(&mut self) {
952        // Clear the pending pings queue and acquire the lock
953        // so that it can't be accessed until this function is done.
954        let _lock = self.upload_manager.clear_ping_queue();
955
956        // Clear any pending pings that follow `collection_enabled`.
957        let ping_maker = PingMaker::new();
958        let disabled_pings = self
959            .ping_registry
960            .iter()
961            .filter(|&(_ping_name, ping)| ping.follows_collection_enabled())
962            .map(|(ping_name, _ping)| &ping_name[..])
963            .collect::<Vec<_>>();
964        if let Err(err) = ping_maker.clear_pending_pings(self.get_data_path(), &disabled_pings) {
965            log::warn!("Error clearing pending pings: {}", err);
966        }
967
968        if let Err(e) = self.remove_stored_client_id() {
969            log::error!("Couldn't remove client ID on disk. This might lead to a resurrection of this client ID later. Error: {e}");
970        }
971
972        // Delete all stored metrics.
973        // Note that this also includes the ping sequence numbers, so it has
974        // the effect of resetting those to their initial values.
975        if let Some(data) = self.data_store.as_ref() {
976            let warn_on_error = |result, msg| {
977                if let Err(e) = result {
978                    log::warn!("{msg}: {e}");
979                }
980            };
981
982            warn_on_error(
983                data.clear_lifetime_storage(Lifetime::User, INTERNAL_STORAGE),
984                "failed to clear internal storage",
985            );
986            warn_on_error(
987                data.remove_single_metric(Lifetime::User, "glean_client_info", "client_id"),
988                "failed to clear internal client info storage",
989            );
990            for (ping_name, ping) in &self.ping_registry {
991                if ping.follows_collection_enabled() {
992                    warn_on_error(
993                        data.clear_ping_lifetime_storage(ping_name),
994                        "failed to clear ping lifetime storage",
995                    );
996                    warn_on_error(
997                        data.clear_lifetime_storage(Lifetime::User, ping_name),
998                        "failed to clear user lifetime storage",
999                    );
1000                    warn_on_error(
1001                        data.clear_lifetime_storage(Lifetime::Application, ping_name),
1002                        "failed to clear application lifetime storage",
1003                    );
1004                }
1005            }
1006        }
1007        if let Err(err) = self.event_data_store.clear_all() {
1008            log::warn!("Error clearing pending events: {}", err);
1009        }
1010
1011        // The persisted `session#*` keys were cleared with internal storage above.
1012        // Reset the in-memory session state to match, so a later opt-in can't
1013        // continue the pre-opt-out session. No `session_end` is recorded, since
1014        // upload is being disabled.
1015        self.session_manager = SessionManager::new(
1016            self.session_manager.mode,
1017            self.session_manager.configured_sample_rate,
1018            self.session_manager.inactivity_timeout,
1019        );
1020
1021        // This does not clear the experiments store (which isn't managed by the
1022        // StorageEngineManager), since doing so would mean we would have to have the
1023        // application tell us again which experiments are active if telemetry is
1024        // re-enabled.
1025    }
1026
1027    /// Gets the application ID as specified on instantiation.
1028    pub fn get_application_id(&self) -> &str {
1029        &self.application_id
1030    }
1031
1032    /// Gets the data path of this instance.
1033    pub fn get_data_path(&self) -> &Path {
1034        &self.data_path
1035    }
1036
1037    /// Gets a handle to the database.
1038    #[track_caller] // If this fails we're interested in the caller.
1039    pub fn storage(&self) -> &Database {
1040        self.data_store.as_ref().expect("No database found")
1041    }
1042
1043    /// Gets an optional handle to the database.
1044    pub fn storage_opt(&self) -> Option<&Database> {
1045        self.data_store.as_ref()
1046    }
1047
1048    /// Gets a handle to the event database.
1049    pub fn event_storage(&self) -> &EventDatabase {
1050        &self.event_data_store
1051    }
1052
1053    /// Gets a reference to the session manager.
1054    pub fn session_manager(&self) -> &SessionManager {
1055        &self.session_manager
1056    }
1057
1058    pub(crate) fn with_timestamps(&self) -> bool {
1059        self.with_timestamps
1060    }
1061
1062    /// Gets the maximum number of events to store before sending a ping.
1063    pub fn get_max_events(&self) -> usize {
1064        let remote_settings_config = self.remote_settings_config.lock().unwrap();
1065
1066        if let Some(max_events) = remote_settings_config.event_threshold {
1067            max_events as usize
1068        } else {
1069            self.max_events as usize
1070        }
1071    }
1072
1073    /// Gets the number of "events" pings to accelerate each session, plus one.
1074    pub fn get_events_ping_acceleration_factor(&self) -> usize {
1075        let remote_settings_config = self.remote_settings_config.lock().unwrap();
1076
1077        if let Some(factor) = remote_settings_config.events_ping_acceleration_factor {
1078            factor
1079        } else {
1080            self.events_ping_acceleration_factor.unwrap_or(1)
1081        }
1082    }
1083
1084    /// Gets the next task for an uploader.
1085    ///
1086    /// This can be one of:
1087    ///
1088    /// * [`Wait`](PingUploadTask::Wait) - which means the requester should ask
1089    ///   again later;
1090    /// * [`Upload(PingRequest)`](PingUploadTask::Upload) - which means there is
1091    ///   a ping to upload. This wraps the actual request object;
1092    /// * [`Done`](PingUploadTask::Done) - which means requester should stop
1093    ///   asking for now.
1094    ///
1095    /// # Returns
1096    ///
1097    /// A [`PingUploadTask`] representing the next task.
1098    pub fn get_upload_task(&self) -> PingUploadTask {
1099        self.upload_manager.get_upload_task(self, self.log_pings())
1100    }
1101
1102    /// Processes the response from an attempt to upload a ping.
1103    ///
1104    /// # Arguments
1105    ///
1106    /// * `uuid` - The UUID of the ping in question.
1107    /// * `status` - The upload result.
1108    pub fn process_ping_upload_response(
1109        &self,
1110        uuid: &str,
1111        status: UploadResult,
1112    ) -> UploadTaskAction {
1113        self.upload_manager
1114            .process_ping_upload_response(self, uuid, status)
1115    }
1116
1117    /// Takes a snapshot for the given store and optionally clear it.
1118    ///
1119    /// # Arguments
1120    ///
1121    /// * `store_name` - The store to snapshot.
1122    /// * `clear_store` - Whether to clear the store after snapshotting.
1123    ///
1124    /// # Returns
1125    ///
1126    /// The snapshot in a string encoded as JSON. If the snapshot is empty, returns an empty string.
1127    pub fn snapshot(&mut self, store_name: &str, clear_store: bool) -> String {
1128        StorageManager
1129            .snapshot(self.storage(), store_name, clear_store)
1130            .unwrap_or_else(|| String::from(""))
1131    }
1132
1133    pub(crate) fn make_path(&self, ping_name: &str, doc_id: &str) -> String {
1134        format!(
1135            "/submit/{}/{}/{}/{}",
1136            self.get_application_id(),
1137            ping_name,
1138            GLEAN_SCHEMA_VERSION,
1139            doc_id
1140        )
1141    }
1142
1143    /// Collects and submits a ping by name for eventual uploading.
1144    ///
1145    /// The ping content is assembled as soon as possible, but upload is not
1146    /// guaranteed to happen immediately, as that depends on the upload policies.
1147    ///
1148    /// If the ping currently contains no content, it will not be sent,
1149    /// unless it is configured to be sent if empty.
1150    ///
1151    /// # Arguments
1152    ///
1153    /// * `ping_name` - The name of the ping to submit
1154    /// * `reason` - A reason code to include in the ping
1155    ///
1156    /// # Returns
1157    ///
1158    /// Whether the ping was succesfully assembled and queued.
1159    ///
1160    /// # Errors
1161    ///
1162    /// If collecting or writing the ping to disk failed.
1163    pub fn submit_ping_by_name(&self, ping_name: &str, reason: Option<&str>) -> bool {
1164        match self.get_ping_by_name(ping_name) {
1165            None => {
1166                log::error!("Attempted to submit unknown ping '{}'", ping_name);
1167                false
1168            }
1169            Some(ping) => ping.submit_sync(self, reason),
1170        }
1171    }
1172
1173    /// Gets a [`PingType`] by name.
1174    ///
1175    /// # Returns
1176    ///
1177    /// The [`PingType`] of a ping if the given name was registered before, [`None`]
1178    /// otherwise.
1179    pub fn get_ping_by_name(&self, ping_name: &str) -> Option<&PingType> {
1180        self.ping_registry.get(ping_name)
1181    }
1182
1183    /// Register a new [`PingType`](metrics/struct.PingType.html).
1184    pub fn register_ping_type(&mut self, ping: &PingType) {
1185        if self.ping_registry.contains_key(ping.name()) {
1186            log::debug!("Duplicate ping named '{}'", ping.name())
1187        }
1188
1189        self.ping_registry
1190            .insert(ping.name().to_string(), ping.clone());
1191    }
1192
1193    /// Gets a list of currently registered ping names.
1194    ///
1195    /// # Returns
1196    ///
1197    /// The list of ping names that are currently registered.
1198    pub fn get_registered_ping_names(&self) -> Vec<&str> {
1199        self.ping_registry.keys().map(String::as_str).collect()
1200    }
1201
1202    /// Get create time of the Glean object.
1203    pub(crate) fn start_time(&self) -> DateTime<FixedOffset> {
1204        self.start_time
1205    }
1206
1207    /// Indicates that an experiment is running.
1208    ///
1209    /// Glean will then add an experiment annotation to the environment
1210    /// which is sent with pings. This information is not persisted between runs.
1211    ///
1212    /// # Arguments
1213    ///
1214    /// * `experiment_id` - The id of the active experiment (maximum 30 bytes).
1215    /// * `branch` - The experiment branch (maximum 30 bytes).
1216    /// * `extra` - Optional metadata to output with the ping.
1217    pub fn set_experiment_active(
1218        &self,
1219        experiment_id: String,
1220        branch: String,
1221        extra: HashMap<String, String>,
1222    ) {
1223        let metric = ExperimentMetric::new(self, experiment_id);
1224        metric.set_active_sync(self, branch, extra);
1225    }
1226
1227    /// Indicates that an experiment is no longer running.
1228    ///
1229    /// # Arguments
1230    ///
1231    /// * `experiment_id` - The id of the active experiment to deactivate (maximum 30 bytes).
1232    pub fn set_experiment_inactive(&self, experiment_id: String) {
1233        let metric = ExperimentMetric::new(self, experiment_id);
1234        metric.set_inactive_sync(self);
1235    }
1236
1237    /// **Test-only API (exported for FFI purposes).**
1238    ///
1239    /// Gets stored data for the requested experiment.
1240    ///
1241    /// # Arguments
1242    ///
1243    /// * `experiment_id` - The id of the active experiment (maximum 30 bytes).
1244    pub fn test_get_experiment_data(&self, experiment_id: String) -> Option<RecordedExperiment> {
1245        let metric = ExperimentMetric::new(self, experiment_id);
1246        metric.test_get_value(self)
1247    }
1248
1249    /// **Test-only API (exported for FFI purposes).**
1250    ///
1251    /// Gets stored experimentation id annotation.
1252    pub fn test_get_experimentation_id(&self) -> Option<String> {
1253        self.additional_metrics
1254            .experimentation_id
1255            .get_value(self, None)
1256    }
1257
1258    /// Set configuration to override the default state, typically initiated from a
1259    /// remote_settings experiment or rollout
1260    ///
1261    /// # Arguments
1262    ///
1263    /// * `cfg` - The stringified JSON representation of a `RemoteSettingsConfig` object
1264    pub fn apply_server_knobs_config(&self, cfg: RemoteSettingsConfig) {
1265        let config_value = {
1266            // Hold the lock while merging config and serializing, then release
1267            // before performing IO in set_sync.
1268            let mut remote_settings_config = self.remote_settings_config.lock().unwrap();
1269
1270            // Merge the exising metrics configuration with the supplied one
1271            remote_settings_config
1272                .metrics_enabled
1273                .extend(cfg.metrics_enabled);
1274
1275            // Merge the exising ping configuration with the supplied one
1276            remote_settings_config
1277                .pings_enabled
1278                .extend(cfg.pings_enabled);
1279
1280            remote_settings_config.event_threshold = cfg.event_threshold;
1281
1282            // Clamp to [0.0, 1.0] so callers can't accidentally set an invalid rate.
1283            //
1284            // NOTE: `session_sample_rate` is intentionally NOT applied to any
1285            // currently-active session.  The override is picked up at the next
1286            // `session_start()` call.  This "sticky per session" design means:
1287            //   - A mid-session RS rollout does not change sampling mid-flight,
1288            //     which would otherwise cause partial session data.
1289            //   - To clear the override and revert to the configured rate, set
1290            //     `session_sample_rate` to `null` in the RS payload.  The next
1291            //     session will use `configured_sample_rate` as the fallback.
1292            //
1293            // This override is intentionally NOT persisted to storage.  Remote
1294            // Settings configuration is refreshed on every app startup, so the
1295            // override will be re-applied before the next session begins.
1296            // Persisting it would risk making a stale value sticky if the RS
1297            // payload changes or is removed between restarts.
1298            remote_settings_config.session_sample_rate = cfg.session_sample_rate.map(|r| {
1299                let clamped = r.clamp(0.0, 1.0);
1300                if clamped != r {
1301                    log::warn!(
1302                        "session_sample_rate {} out of range, clamped to {}",
1303                        r,
1304                        clamped
1305                    );
1306                }
1307                clamped
1308            });
1309
1310            remote_settings_config.events_ping_acceleration_factor =
1311                cfg.events_ping_acceleration_factor;
1312
1313            // Store the Server Knobs configuration as an ObjectMetric
1314            // Since RemoteSettingsConfig only contains maps with string keys and primitives,
1315            // serialization via the derived Serialize impl cannot fail so it is safe to unwrap.
1316            serde_json::to_value(&*remote_settings_config).unwrap()
1317        };
1318
1319        self.additional_metrics
1320            .server_knobs_config
1321            .set_sync(self, config_value);
1322
1323        // Update remote_settings epoch
1324        self.remote_settings_epoch.fetch_add(1, Ordering::SeqCst);
1325    }
1326
1327    /// Persists [`Lifetime::Ping`] data that might be in memory in case
1328    /// [`delay_ping_lifetime_io`](InternalConfiguration::delay_ping_lifetime_io) is set
1329    /// or was set at a previous time.
1330    ///
1331    /// If there is no data to persist, this function does nothing.
1332    pub fn persist_ping_lifetime_data(&self) -> Result<()> {
1333        if let Some(data) = self.data_store.as_ref() {
1334            return data.persist_ping_lifetime_data();
1335        }
1336
1337        Ok(())
1338    }
1339
1340    /// Set early metrics unconditionally.
1341    ///
1342    /// This covers e.g. the `first_run_date` and application lifetime metrics known at build time.
1343    fn set_early_metrics(&mut self) {
1344        // We set the first_run_date unconditionally nowadays
1345        if self
1346            .core_metrics
1347            .first_run_date
1348            .get_value(self, "glean_client_info")
1349            .is_none()
1350        {
1351            self.core_metrics.first_run_date.set_sync(self, None);
1352            // The `first_run_date` field is generated on the very first run
1353            // and persisted across upload toggling. We can assume that, the only
1354            // time it is set, that's indeed our "first run".
1355            self.is_first_run = true;
1356        }
1357        self.set_application_lifetime_core_metrics();
1358    }
1359
1360    /// Sets internally-handled application lifetime metrics.
1361    fn set_application_lifetime_core_metrics(&self) {
1362        self.core_metrics.os.set_sync(self, system::OS);
1363    }
1364
1365    /// **This is not meant to be used directly.**
1366    ///
1367    /// Clears all the metrics that have [`Lifetime::Application`].
1368    pub fn clear_application_lifetime_metrics(&self) {
1369        log::trace!("Clearing Lifetime::Application metrics");
1370        if let Some(data) = self.data_store.as_ref() {
1371            data.clear_lifetime(Lifetime::Application);
1372        }
1373
1374        // Set internally handled app lifetime metrics again.
1375        self.set_application_lifetime_core_metrics();
1376    }
1377
1378    /// Whether or not this is the first run on this profile.
1379    pub fn is_first_run(&self) -> bool {
1380        self.is_first_run
1381    }
1382
1383    /// Sets a debug view tag.
1384    ///
1385    /// This will return `false` in case `value` is not a valid tag.
1386    ///
1387    /// When the debug view tag is set, pings are sent with a `X-Debug-ID` header with the value of the tag
1388    /// and are sent to the ["Ping Debug Viewer"](https://mozilla.github.io/glean/book/dev/core/internal/debug-pings.html).
1389    ///
1390    /// # Arguments
1391    ///
1392    /// * `value` - A valid HTTP header value. Must match the regex: "[a-zA-Z0-9-]{1,20}".
1393    pub fn set_debug_view_tag(&mut self, value: &str) -> bool {
1394        self.debug.debug_view_tag.set(value.into())
1395    }
1396
1397    /// Return the value for the debug view tag or [`None`] if it hasn't been set.
1398    ///
1399    /// The `debug_view_tag` may be set from an environment variable
1400    /// (`GLEAN_DEBUG_VIEW_TAG`) or through the [`set_debug_view_tag`](Glean::set_debug_view_tag) function.
1401    pub fn debug_view_tag(&self) -> Option<&String> {
1402        self.debug.debug_view_tag.get()
1403    }
1404
1405    /// Sets source tags.
1406    ///
1407    /// This will return `false` in case `value` contains invalid tags.
1408    ///
1409    /// Ping tags will show in the destination datasets, after ingestion.
1410    ///
1411    /// **Note** If one or more tags are invalid, all tags are ignored.
1412    ///
1413    /// # Arguments
1414    ///
1415    /// * `value` - A vector of at most 5 valid HTTP header values. Individual tags must match the regex: "[a-zA-Z0-9-]{1,20}".
1416    pub fn set_source_tags(&mut self, value: Vec<String>) -> bool {
1417        self.debug.source_tags.set(value)
1418    }
1419
1420    /// Return the value for the source tags or [`None`] if it hasn't been set.
1421    ///
1422    /// The `source_tags` may be set from an environment variable (`GLEAN_SOURCE_TAGS`)
1423    /// or through the [`set_source_tags`](Glean::set_source_tags) function.
1424    pub(crate) fn source_tags(&self) -> Option<&Vec<String>> {
1425        self.debug.source_tags.get()
1426    }
1427
1428    /// Sets the log pings debug option.
1429    ///
1430    /// This will return `false` in case we are unable to set the option.
1431    ///
1432    /// When the log pings debug option is `true`,
1433    /// we log the payload of all succesfully assembled pings.
1434    ///
1435    /// # Arguments
1436    ///
1437    /// * `value` - The value of the log pings option
1438    pub fn set_log_pings(&mut self, value: bool) -> bool {
1439        self.debug.log_pings.set(value)
1440    }
1441
1442    /// Return the value for the log pings debug option or `false` if it hasn't been set.
1443    ///
1444    /// The `log_pings` option may be set from an environment variable (`GLEAN_LOG_PINGS`)
1445    /// or through the `set_log_pings` function.
1446    pub fn log_pings(&self) -> bool {
1447        self.debug.log_pings.get().copied().unwrap_or(false)
1448    }
1449
1450    fn get_dirty_bit_metric(&self) -> metrics::BooleanMetric {
1451        metrics::BooleanMetric::new(CommonMetricData {
1452            name: "dirtybit".into(),
1453            // We don't need a category, the name is already unique
1454            category: "".into(),
1455            send_in_pings: vec![INTERNAL_STORAGE.into()],
1456            lifetime: Lifetime::User,
1457            ..Default::default()
1458        })
1459    }
1460
1461    /// **This is not meant to be used directly.**
1462    ///
1463    /// Sets the value of a "dirty flag" in the permanent storage.
1464    ///
1465    /// The "dirty flag" is meant to have the following behaviour, implemented
1466    /// by the consumers of the FFI layer:
1467    ///
1468    /// - on mobile: set to `false` when going to background or shutting down,
1469    ///   set to `true` at startup and when going to foreground.
1470    /// - on non-mobile platforms: set to `true` at startup and `false` at
1471    ///   shutdown.
1472    ///
1473    /// At startup, before setting its new value, if the "dirty flag" value is
1474    /// `true`, then Glean knows it did not exit cleanly and can implement
1475    /// coping mechanisms (e.g. sending a `baseline` ping).
1476    pub fn set_dirty_flag(&self, new_value: bool) {
1477        self.get_dirty_bit_metric().set_sync(self, new_value);
1478    }
1479
1480    /// **This is not meant to be used directly.**
1481    ///
1482    /// Checks the stored value of the "dirty flag".
1483    pub fn is_dirty_flag_set(&self) -> bool {
1484        let dirty_bit_metric = self.get_dirty_bit_metric();
1485        match self.storage().get_metric(
1486            #[cfg(not(feature = "sqlite"))]
1487            self,
1488            dirty_bit_metric.meta(),
1489            INTERNAL_STORAGE,
1490        ) {
1491            Some(Metric::Boolean(b)) => b,
1492            _ => false,
1493        }
1494    }
1495
1496    // -----------------------------------------------------------------------
1497    // Session lifecycle methods
1498    // -----------------------------------------------------------------------
1499
1500    /// Restores session state from persistent storage at startup.
1501    ///
1502    /// Must be called after `data_store` is initialized (i.e. after
1503    /// `Database::new` succeeds) so that the storage reads are valid.
1504    ///
1505    /// **Sequence counter**: `session_seq` is always restored so it is
1506    /// monotonically increasing across restarts.  Note that if a crash occurs
1507    /// between `store_session_seq` and `persist_session_id` inside
1508    /// `session_start`, the sequence number will have been incremented but no
1509    /// session ID will be persisted.  On the next restart this method will
1510    /// restore the incremented seq and the next session will be assigned
1511    /// seq+1, leaving a one-element gap.  This is acceptable — downstream
1512    /// analysts should treat sequence numbers as monotonically non-decreasing,
1513    /// not strictly contiguous.
1514    ///
1515    /// **AUTO mode resumption**: requires both a persisted `session_id` **and**
1516    /// an `inactive_since` timestamp.  If either is absent the previous session
1517    /// is considered abandoned and the next `handle_client_active` call will
1518    /// start a fresh session via `session_start()`.  On a crash restart,
1519    /// `recover_session_on_dirty_flag()` overwrites whatever this method
1520    /// restores, so the dirty-flag path is always authoritative.
1521    fn restore_session_state_from_storage(&mut self) {
1522        // Always restore seq so new sessions increment from the last known value.
1523        self.session_manager.session_seq = session::read_session_seq(self);
1524
1525        // Check for an orphaned session from a previous build that used a
1526        // different SessionMode.  If the current mode would not restore the
1527        // persisted session, emit a synthetic session_end("abandoned") and
1528        // clear all persisted session state so it doesn't leak across builds.
1529        if self.session_manager.mode != SessionMode::Auto {
1530            if let Some(id_str) = session::read_session_id(self) {
1531                log::info!(
1532                    "Orphaned session {} found from a previous Auto-mode build; \
1533                     emitting session_end(\"abandoned\") and clearing storage",
1534                    id_str
1535                );
1536                let seq = self.session_manager.session_seq;
1537                self.record_session_end_event(&id_str, seq, Some("abandoned"));
1538                session::clear(self);
1539            }
1540            return;
1541        }
1542
1543        // AUTO mode: restore inactive session state so inactivity timeout
1544        // evaluation can happen lazily on the next handle_client_active call.
1545        if let Some(inactive_since) = session::read_inactive_since(self) {
1546            if let Some(id_str) = session::read_session_id(self) {
1547                if let Ok(id) = Uuid::parse_str(&id_str) {
1548                    // Recompute sampled_in deterministically from the UUID so
1549                    // the sampling decision is consistent across the resumed session.
1550                    let sampled_in = session::uuid_to_sample_value(&id)
1551                        < self.session_manager.configured_sample_rate;
1552                    self.session_manager.session_id = Some(id);
1553                    self.session_manager.inactive_since = Some(inactive_since);
1554                    self.session_manager.sampled_in = sampled_in;
1555                    self.session_manager.session_start_time =
1556                        session::read_session_start_time(self);
1557                    if self.session_manager.session_start_time.is_none() {
1558                        log::warn!(
1559                            "Resumed session {} has no persisted session_start_time; \
1560                             events in this session will carry session_start_time: null",
1561                            id
1562                        );
1563                    }
1564                    // Restore event_seq so the resumed session issues
1565                    // monotonically increasing sequence numbers even across
1566                    // a clean restart.
1567                    self.session_manager
1568                        .event_seq
1569                        .store(session::read_session_event_seq(self), Ordering::Relaxed);
1570                    self.session_manager.state = SessionState::Inactive;
1571                }
1572            }
1573        }
1574    }
1575
1576    /// Injects a `glean_timestamp` key into `extra` when event timestamps are enabled.
1577    ///
1578    /// Uses the wall clock, matching `EventMetric::record`. The event's main
1579    /// `timestamp` stays on the monotonic clock.
1580    fn maybe_inject_glean_timestamp(&self, extra: &mut std::collections::HashMap<String, String>) {
1581        if self.with_timestamps {
1582            let precise_timestamp = Utc::now().timestamp_millis() as u64;
1583            extra.insert("glean_timestamp".to_string(), precise_timestamp.to_string());
1584        }
1585    }
1586
1587    /// Records a `glean.session_start` boundary event (always, regardless of sampling).
1588    fn record_session_start_event(
1589        &self,
1590        session_id: &str,
1591        seq: u64,
1592        start_time: DateTime<FixedOffset>,
1593        sampled_in: bool,
1594    ) {
1595        let meta = CommonMetricData {
1596            name: "session_start".into(),
1597            category: "glean".into(),
1598            send_in_pings: vec!["events".into()],
1599            lifetime: Lifetime::Ping,
1600            ..Default::default()
1601        };
1602        let timestamp = crate::get_timestamp_ms();
1603        let mut extra = std::collections::HashMap::new();
1604        extra.insert("session_id".to_string(), session_id.to_string());
1605        extra.insert("session_seq".to_string(), seq.to_string());
1606        extra.insert(
1607            "session_start_time".to_string(),
1608            start_time.to_rfc3339_opts(SecondsFormat::Millis, true),
1609        );
1610        extra.insert("sampled_in".to_string(), sampled_in.to_string());
1611        self.maybe_inject_glean_timestamp(&mut extra);
1612        self.event_data_store.record(
1613            self,
1614            &meta.into(),
1615            timestamp,
1616            Some(extra),
1617            EventSessionContext::OutOfSession,
1618        );
1619    }
1620
1621    /// Records a `glean.session_end` boundary event (always, regardless of sampling).
1622    fn record_session_end_event(&self, session_id: &str, seq: u64, reason: Option<&str>) {
1623        let meta = CommonMetricData {
1624            name: "session_end".into(),
1625            category: "glean".into(),
1626            send_in_pings: vec!["events".into()],
1627            lifetime: Lifetime::Ping,
1628            ..Default::default()
1629        };
1630        let timestamp = crate::get_timestamp_ms();
1631        let mut extra = std::collections::HashMap::new();
1632        extra.insert("session_id".to_string(), session_id.to_string());
1633        extra.insert("session_seq".to_string(), seq.to_string());
1634        if let Some(r) = reason {
1635            extra.insert("reason".to_string(), r.to_string());
1636        }
1637        self.maybe_inject_glean_timestamp(&mut extra);
1638        self.event_data_store.record(
1639            self,
1640            &meta.into(),
1641            timestamp,
1642            Some(extra),
1643            EventSessionContext::OutOfSession,
1644        );
1645    }
1646
1647    /// Starts a new session, persists state, and records a boundary event.
1648    ///
1649    /// If a session is already active it is ended cleanly before the new one
1650    /// starts, preventing orphaned sessions with no corresponding `session_end`.
1651    pub fn session_start(&mut self) {
1652        // End any already-active session so we never orphan a session_end event.
1653        if self.session_manager.is_active() {
1654            self.session_end(Some("replaced"));
1655        }
1656
1657        // 1. Compute new seq from in-memory value (authoritative after init).
1658        let new_seq = self.session_manager.session_seq + 1;
1659
1660        // 2. Generate new session_id and compute sampling.
1661        //    Prefer a remote-settings override if one has been set, falling back
1662        //    to the immutable configured_sample_rate (never the last effective
1663        //    rate) so RS overrides can be fully cleared without residual effects.
1664        //    The rate is sampled once here and is sticky for the entire session;
1665        //    any RS update received mid-session takes effect at the next session_start.
1666        let session_id = uuid::Uuid::new_v4();
1667        let sample_rate = {
1668            let remote = self.remote_settings_config.lock().unwrap();
1669            remote
1670                .session_sample_rate
1671                .unwrap_or(self.session_manager.configured_sample_rate)
1672        };
1673        let sampled_in = session::uuid_to_sample_value(&session_id) < sample_rate;
1674
1675        // 3. Update in-memory state.
1676        self.session_manager.sample_rate = sample_rate;
1677        // Truncate to millisecond precision so that in-memory and persisted
1678        // (RFC 3339 millis) representations are identical after a round-trip.
1679        let start_time = {
1680            let now = local_now_with_offset();
1681            let millis = now.timestamp_millis();
1682            DateTime::from_timestamp_millis(millis)
1683                .expect("valid timestamp")
1684                .with_timezone(now.offset())
1685        };
1686        self.session_manager.session_start_time = Some(start_time);
1687        self.session_manager.session_id = Some(session_id);
1688        self.session_manager.session_seq = new_seq;
1689        self.session_manager.event_seq.store(0, Ordering::Relaxed);
1690        self.session_manager.sampled_in = sampled_in;
1691        self.session_manager.state = SessionState::Active;
1692        self.session_manager.inactive_since = None;
1693
1694        // 4. Persist to storage.
1695        session::store_session_seq(self, new_seq);
1696        session::persist_session_id(self, &session_id.to_string());
1697        session::persist_session_start_time(self, start_time);
1698        session::clear_inactive_since(self);
1699
1700        // 5. Increment diagnostic counter.
1701        self.additional_metrics.sessions_seen.add_sync(self, 1);
1702
1703        // 6. Record boundary event.
1704        self.record_session_start_event(&session_id.to_string(), new_seq, start_time, sampled_in);
1705    }
1706
1707    /// Ends the current session, persists state, and records a boundary event.
1708    ///
1709    /// Returns the ended session's metadata, or `None` if no session was active.
1710    pub fn session_end(&mut self, reason: Option<&str>) -> Option<crate::session::SessionMetadata> {
1711        if self.session_manager.state != SessionState::Active {
1712            return None;
1713        }
1714
1715        let session_id = self.session_manager.session_id?;
1716        let seq = self.session_manager.session_seq;
1717        let event_seq = self.session_manager.event_seq.load(Ordering::Relaxed);
1718        let sample_rate = self.session_manager.sample_rate;
1719        let start_time = self.session_manager.session_start_time;
1720
1721        // Clear persistence.
1722        session::clear(self);
1723
1724        // Reset in-memory state so the next session_start gets a clean slate.
1725        self.session_manager.reset_state();
1726
1727        // Record boundary event.
1728        self.record_session_end_event(&session_id.to_string(), seq, reason);
1729
1730        Some(crate::session::SessionMetadata {
1731            session_id: session_id.to_string(),
1732            session_seq: seq,
1733            event_seq,
1734            session_sample_rate: sample_rate,
1735            session_start_time: start_time.map(|t| t.to_rfc3339_opts(SecondsFormat::Millis, true)),
1736        })
1737    }
1738
1739    /// Transitions the current session to inactive (AUTO mode).
1740    ///
1741    /// Records the `inactive_since` timestamp for timeout evaluation on next activation.
1742    /// Does NOT end the session — that happens lazily on next `handle_client_active`.
1743    pub(crate) fn session_transition_to_inactive(&mut self) {
1744        if self.session_manager.state != SessionState::Active {
1745            return;
1746        }
1747
1748        let now = local_now_with_offset();
1749        // Snapshot event_seq before changing state so the value is stable.
1750        let event_seq = self.session_manager.event_seq.load(Ordering::Relaxed);
1751        self.session_manager.state = SessionState::Inactive;
1752        self.session_manager.inactive_since = Some(now);
1753
1754        // Persist for crash recovery and clean-restart resumption.
1755        // event_seq is persisted here (rather than on every increment) because
1756        // this is the only point where events stop being recorded mid-session;
1757        // if the app crashes before the next activation, the recovered session
1758        // will at least have the correct seq baseline from the last inactive
1759        // transition.
1760        session::persist_inactive_since(self, now);
1761        session::store_session_event_seq(self, event_seq);
1762    }
1763
1764    /// Handles transitioning from inactive to active (AUTO mode).
1765    ///
1766    /// Evaluates the inactivity timeout:
1767    /// - If the timeout has NOT expired: resume the existing session.
1768    /// - If the timeout HAS expired: end the old session and start a new one.
1769    ///
1770    /// Returns `true` if a new session was started.
1771    pub(crate) fn session_transition_to_active(&mut self) -> bool {
1772        match self.session_manager.inactive_since {
1773            None => {
1774                // No inactive_since recorded: treat as a cold activation and start
1775                // a fresh session.  The call site in handle_client_active guards
1776                // with `inactive_since.is_some()` so this is normally unreachable,
1777                // but we handle it safely rather than leaving state inconsistent.
1778                self.session_start();
1779                true
1780            }
1781            Some(inactive_since) => {
1782                let now = local_now_with_offset();
1783                let elapsed = (now - inactive_since).to_std().unwrap_or_default();
1784
1785                // A timeout of zero means "never time out" (session always resumes).
1786                if !self.session_manager.inactivity_timeout.is_zero()
1787                    && elapsed >= self.session_manager.inactivity_timeout
1788                {
1789                    // Timeout expired → end old session (emits boundary event), start new one.
1790                    // The session state was set to Inactive by session_transition_to_inactive(),
1791                    // but session_id is still set. Restore Active so session_end() can proceed.
1792                    self.session_manager.state = SessionState::Active;
1793                    self.session_end(Some("timeout"));
1794                    self.session_start();
1795                    true
1796                } else {
1797                    // Timeout has NOT expired → resume existing session.
1798                    self.session_manager.state = SessionState::Active;
1799                    self.session_manager.inactive_since = None;
1800                    session::clear_inactive_since(self);
1801                    false
1802                }
1803            }
1804        }
1805    }
1806
1807    /// Called during initialization to recover an abnormally terminated session.
1808    ///
1809    /// If the dirty flag was set and a session ID is persisted, emits a synthetic
1810    /// `session_end` event with reason "abnormal" and clears session state.
1811    pub(crate) fn recover_session_on_dirty_flag(&mut self) {
1812        let persisted_id = match session::read_session_id(self) {
1813            Some(id) => id,
1814            None => return, // No previous session to recover.
1815        };
1816
1817        let persisted_seq = self.session_manager.session_seq;
1818        let inactive_since = session::read_inactive_since(self);
1819
1820        // Determine if the session ended while inactive (timeout may have expired).
1821        let reason = if inactive_since.is_some() {
1822            "abnormal_inactive"
1823        } else {
1824            "abnormal"
1825        };
1826
1827        log::info!(
1828            "Recovering abnormally terminated session: {} (seq={})",
1829            persisted_id,
1830            persisted_seq
1831        );
1832
1833        // Emit synthetic session_end.
1834        self.record_session_end_event(&persisted_id, persisted_seq, Some(reason));
1835
1836        // Clear persisted session state so the recovered session won't be replayed.
1837        session::clear(self);
1838
1839        // Reset in-memory state so the next session_start gets a clean slate.
1840        self.session_manager.reset_state();
1841    }
1842
1843    // -----------------------------------------------------------------------
1844    // Client lifecycle methods
1845    // -----------------------------------------------------------------------
1846
1847    /// Performs the collection/cleanup operations required by becoming active.
1848    ///
1849    /// This functions generates a baseline ping with reason `active`
1850    /// and then sets the dirty bit.
1851    pub fn handle_client_active(&mut self) {
1852        match self.session_manager.mode {
1853            SessionMode::Auto => {
1854                if !self.session_manager.is_active() {
1855                    if self.session_manager.inactive_since.is_some() {
1856                        // Was inactive — evaluate timeout.
1857                        self.session_transition_to_active();
1858                    } else {
1859                        // First activation — start initial session.
1860                        self.session_start();
1861                    }
1862                }
1863            }
1864            SessionMode::Lifecycle => {
1865                // Only start a session on the first activation following an inactive
1866                // transition. Guard against duplicate handle_client_active calls which
1867                // are not a real lifecycle transition.
1868                if !self.session_manager.is_active() {
1869                    self.session_start();
1870                }
1871            }
1872            SessionMode::Manual => {
1873                // No automatic session management.
1874            }
1875        }
1876
1877        if !self
1878            .internal_pings
1879            .baseline
1880            .submit_sync(self, Some("active"))
1881        {
1882            log::info!("baseline ping not submitted on active");
1883        }
1884
1885        self.set_dirty_flag(true);
1886    }
1887
1888    /// Performs the collection/cleanup operations required by becoming inactive.
1889    ///
1890    /// This functions generates a baseline and an events ping with reason
1891    /// `inactive` and then clears the dirty bit.
1892    pub fn handle_client_inactive(&mut self) {
1893        match self.session_manager.mode {
1894            SessionMode::Auto => {
1895                // In AUTO mode, don't end the session immediately. Instead record
1896                // inactive_since for lazy timeout evaluation on next activation.
1897                self.session_transition_to_inactive();
1898            }
1899            SessionMode::Lifecycle => {
1900                // End session immediately on going inactive.
1901                self.session_end(Some("inactive"));
1902            }
1903            SessionMode::Manual => {
1904                // No automatic session management.
1905            }
1906        }
1907
1908        if !self
1909            .internal_pings
1910            .baseline
1911            .submit_sync(self, Some("inactive"))
1912        {
1913            log::info!("baseline ping not submitted on inactive");
1914        }
1915
1916        if !self
1917            .internal_pings
1918            .events
1919            .submit_sync(self, Some("inactive"))
1920        {
1921            log::info!("events ping not submitted on inactive");
1922        }
1923
1924        self.set_dirty_flag(false);
1925    }
1926
1927    /// **Test-only API (exported for FFI purposes).**
1928    ///
1929    /// Deletes all stored metrics.
1930    ///
1931    /// Note that this also includes the ping sequence numbers, so it has
1932    /// the effect of resetting those to their initial values.
1933    pub fn test_clear_all_stores(&self) {
1934        if let Some(data) = self.data_store.as_ref() {
1935            data.clear_all()
1936        }
1937        // We don't care about this failing, maybe the data does just not exist.
1938        let _ = self.event_data_store.clear_all();
1939    }
1940
1941    /// Instructs the Metrics Ping Scheduler's thread to exit cleanly.
1942    /// If Glean was configured with `use_core_mps: false`, this has no effect.
1943    pub fn cancel_metrics_ping_scheduler(&self) {
1944        if self.schedule_metrics_pings {
1945            scheduler::cancel();
1946        }
1947    }
1948
1949    /// Instructs the Metrics Ping Scheduler to being scheduling metrics pings.
1950    /// If Glean wsa configured with `use_core_mps: false`, this has no effect.
1951    pub fn start_metrics_ping_scheduler(&self) {
1952        if self.schedule_metrics_pings {
1953            scheduler::schedule(self);
1954        }
1955    }
1956
1957    /// Clears the core attribution data.
1958    /// Does not clear glean.attribution.ext.
1959    pub fn clear_attribution(&self) {
1960        if let Some(data) = self.data_store.as_ref() {
1961            [
1962                &self.core_metrics.attribution_source,
1963                &self.core_metrics.attribution_medium,
1964                &self.core_metrics.attribution_campaign,
1965                &self.core_metrics.attribution_term,
1966                &self.core_metrics.attribution_content,
1967            ]
1968            .iter()
1969            .for_each(|metric| {
1970                let meta = metric.meta();
1971                _ = data.remove_single_metric(
1972                    meta.inner.lifetime,
1973                    &meta.storage_names()[0],
1974                    &meta.base_identifier(),
1975                );
1976            });
1977        }
1978    }
1979
1980    /// Updates attribution fields with new values.
1981    /// AttributionMetrics fields with `None` values will not overwrite older values.
1982    pub fn update_attribution(&self, attribution: AttributionMetrics) {
1983        const MAX_ATTRIBUTION_LENGTH: usize = 512;
1984
1985        if let Some(source) = attribution.source {
1986            self.core_metrics
1987                .attribution_source
1988                .set_sync_with_max_length(self, source, MAX_ATTRIBUTION_LENGTH);
1989        }
1990        if let Some(medium) = attribution.medium {
1991            self.core_metrics
1992                .attribution_medium
1993                .set_sync_with_max_length(self, medium, MAX_ATTRIBUTION_LENGTH);
1994        }
1995        if let Some(campaign) = attribution.campaign {
1996            self.core_metrics
1997                .attribution_campaign
1998                .set_sync_with_max_length(self, campaign, MAX_ATTRIBUTION_LENGTH);
1999        }
2000        if let Some(term) = attribution.term {
2001            self.core_metrics.attribution_term.set_sync_with_max_length(
2002                self,
2003                term,
2004                MAX_ATTRIBUTION_LENGTH,
2005            );
2006        }
2007        if let Some(content) = attribution.content {
2008            self.core_metrics
2009                .attribution_content
2010                .set_sync_with_max_length(self, content, MAX_ATTRIBUTION_LENGTH);
2011        }
2012    }
2013
2014    /// **TEST-ONLY Method**
2015    ///
2016    /// Returns the current attribution metrics.
2017    pub fn test_get_attribution(&self) -> AttributionMetrics {
2018        AttributionMetrics {
2019            source: self
2020                .core_metrics
2021                .attribution_source
2022                .get_value(self, Some("glean_client_info")),
2023            medium: self
2024                .core_metrics
2025                .attribution_medium
2026                .get_value(self, Some("glean_client_info")),
2027            campaign: self
2028                .core_metrics
2029                .attribution_campaign
2030                .get_value(self, Some("glean_client_info")),
2031            term: self
2032                .core_metrics
2033                .attribution_term
2034                .get_value(self, Some("glean_client_info")),
2035            content: self
2036                .core_metrics
2037                .attribution_content
2038                .get_value(self, Some("glean_client_info")),
2039        }
2040    }
2041
2042    /// Clears the core distribution data.
2043    /// Does not clear glean.distribution.ext.
2044    pub fn clear_distribution(&self) {
2045        if let Some(data) = self.data_store.as_ref() {
2046            let meta = self.core_metrics.distribution_name.meta();
2047            _ = data.remove_single_metric(
2048                meta.inner.lifetime,
2049                &meta.storage_names()[0],
2050                &meta.base_identifier(),
2051            );
2052        }
2053    }
2054
2055    /// Updates distribution fields with new values.
2056    /// DistributionMetrics fields with `None` values will not overwrite older values.
2057    pub fn update_distribution(&self, distribution: DistributionMetrics) {
2058        if let Some(name) = distribution.name {
2059            self.core_metrics.distribution_name.set_sync(self, name);
2060        }
2061    }
2062
2063    /// **TEST-ONLY Method**
2064    ///
2065    /// Returns the current distribution metrics.
2066    pub fn test_get_distribution(&self) -> DistributionMetrics {
2067        DistributionMetrics {
2068            name: self
2069                .core_metrics
2070                .distribution_name
2071                .get_value(self, Some("glean_client_info")),
2072        }
2073    }
2074}