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::path::{Path, PathBuf};
7use std::sync::atomic::{AtomicU8, Ordering};
8use std::sync::{Arc, Mutex};
9use std::time::Duration;
10
11use chrono::{DateTime, FixedOffset};
12use malloc_size_of_derive::MallocSizeOf;
13use once_cell::sync::OnceCell;
14
15use crate::database::Database;
16use crate::debug::DebugOptions;
17use crate::event_database::EventDatabase;
18use crate::internal_metrics::{AdditionalMetrics, CoreMetrics, DatabaseMetrics, HealthMetrics};
19use crate::internal_pings::InternalPings;
20use crate::metrics::{
21    self, ExperimentMetric, Metric, MetricType, PingType, RecordedExperiment, RemoteSettingsConfig,
22};
23use crate::ping::PingMaker;
24use crate::storage::{StorageManager, INTERNAL_STORAGE};
25use crate::upload::{PingUploadManager, PingUploadTask, UploadResult, UploadTaskAction};
26use crate::util::{local_now_with_offset, sanitize_application_id};
27use crate::{
28    scheduler, system, AttributionMetrics, CommonMetricData, DistributionMetrics, ErrorKind,
29    InternalConfiguration, Lifetime, PingRateLimit, Result, DEFAULT_MAX_EVENTS,
30    GLEAN_SCHEMA_VERSION, GLEAN_VERSION, KNOWN_CLIENT_ID,
31};
32
33static GLEAN: OnceCell<Mutex<Glean>> = OnceCell::new();
34
35pub fn global_glean() -> Option<&'static Mutex<Glean>> {
36    GLEAN.get()
37}
38
39/// Sets or replaces the global Glean object.
40pub fn setup_glean(glean: Glean) -> Result<()> {
41    // The `OnceCell` type wrapping our Glean is thread-safe and can only be set once.
42    // Therefore even if our check for it being empty succeeds, setting it could fail if a
43    // concurrent thread is quicker in setting it.
44    // However this will not cause a bigger problem, as the second `set` operation will just fail.
45    // We can log it and move on.
46    //
47    // For all wrappers this is not a problem, as the Glean object is intialized exactly once on
48    // calling `initialize` on the global singleton and further operations check that it has been
49    // initialized.
50    if GLEAN.get().is_none() {
51        if GLEAN.set(Mutex::new(glean)).is_err() {
52            log::warn!(
53                "Global Glean object is initialized already. This probably happened concurrently."
54            )
55        }
56    } else {
57        // We allow overriding the global Glean object to support test mode.
58        // In test mode the Glean object is fully destroyed and recreated.
59        // This all happens behind a mutex and is therefore also thread-safe..
60        let mut lock = GLEAN.get().unwrap().lock().unwrap();
61        *lock = glean;
62    }
63    Ok(())
64}
65
66/// Execute `f` passing the global Glean object.
67///
68/// Panics if the global Glean object has not been set.
69pub fn with_glean<F, R>(f: F) -> R
70where
71    F: FnOnce(&Glean) -> R,
72{
73    let glean = global_glean().expect("Global Glean object not initialized");
74    let lock = glean.lock().unwrap();
75    f(&lock)
76}
77
78/// Execute `f` passing the global Glean object mutable.
79///
80/// Panics if the global Glean object has not been set.
81pub fn with_glean_mut<F, R>(f: F) -> R
82where
83    F: FnOnce(&mut Glean) -> R,
84{
85    let glean = global_glean().expect("Global Glean object not initialized");
86    let mut lock = glean.lock().unwrap();
87    f(&mut lock)
88}
89
90/// Execute `f` passing the global Glean object if it has been set.
91///
92/// Returns `None` if the global Glean object has not been set.
93/// Returns `Some(T)` otherwise.
94pub fn with_opt_glean<F, R>(f: F) -> Option<R>
95where
96    F: FnOnce(&Glean) -> R,
97{
98    let glean = global_glean()?;
99    let lock = glean.lock().unwrap();
100    Some(f(&lock))
101}
102
103/// The object holding meta information about a Glean instance.
104///
105/// ## Example
106///
107/// Create a new Glean instance, register a ping, record a simple counter and then send the final
108/// ping.
109///
110/// ```rust,no_run
111/// # use glean_core::{Glean, InternalConfiguration, CommonMetricData, metrics::*};
112/// let cfg = InternalConfiguration {
113///     data_path: "/tmp/glean".into(),
114///     application_id: "glean.sample.app".into(),
115///     language_binding_name: "Rust".into(),
116///     upload_enabled: true,
117///     max_events: None,
118///     delay_ping_lifetime_io: false,
119///     app_build: "".into(),
120///     use_core_mps: false,
121///     trim_data_to_registered_pings: false,
122///     log_level: None,
123///     rate_limit: None,
124///     enable_event_timestamps: true,
125///     experimentation_id: None,
126///     enable_internal_pings: true,
127///     ping_schedule: Default::default(),
128///     ping_lifetime_threshold: 1000,
129///     ping_lifetime_max_time: 2000,
130/// };
131/// let mut glean = Glean::new(cfg).unwrap();
132/// let ping = PingType::new("sample", true, false, true, true, true, vec![], vec![], true, vec![]);
133/// glean.register_ping_type(&ping);
134///
135/// let call_counter: CounterMetric = CounterMetric::new(CommonMetricData {
136///     name: "calls".into(),
137///     category: "local".into(),
138///     send_in_pings: vec!["sample".into()],
139///     ..Default::default()
140/// });
141///
142/// call_counter.add_sync(&glean, 1);
143///
144/// ping.submit_sync(&glean, None);
145/// ```
146///
147/// ## Note
148///
149/// In specific language bindings, this is usually wrapped in a singleton and all metric recording goes to a single instance of this object.
150/// In the Rust core, it is possible to create multiple instances, which is used in testing.
151#[derive(Debug, MallocSizeOf)]
152pub struct Glean {
153    upload_enabled: bool,
154    pub(crate) data_store: Option<Database>,
155    event_data_store: EventDatabase,
156    pub(crate) core_metrics: CoreMetrics,
157    pub(crate) additional_metrics: AdditionalMetrics,
158    pub(crate) database_metrics: DatabaseMetrics,
159    pub(crate) health_metrics: HealthMetrics,
160    pub(crate) internal_pings: InternalPings,
161    data_path: PathBuf,
162    application_id: String,
163    ping_registry: HashMap<String, PingType>,
164    #[ignore_malloc_size_of = "external non-allocating type"]
165    start_time: DateTime<FixedOffset>,
166    max_events: u32,
167    is_first_run: bool,
168    pub(crate) upload_manager: PingUploadManager,
169    debug: DebugOptions,
170    pub(crate) app_build: String,
171    pub(crate) schedule_metrics_pings: bool,
172    pub(crate) remote_settings_epoch: AtomicU8,
173    #[ignore_malloc_size_of = "TODO: Expose Glean's inner memory allocations (bug 1960592)"]
174    pub(crate) remote_settings_config: Arc<Mutex<RemoteSettingsConfig>>,
175    pub(crate) with_timestamps: bool,
176    pub(crate) ping_schedule: HashMap<String, Vec<String>>,
177}
178
179impl Glean {
180    /// Creates and initializes a new Glean object for use in a subprocess.
181    ///
182    /// Importantly, this will not send any pings at startup, since that
183    /// sort of management should only happen in the main process.
184    pub fn new_for_subprocess(cfg: &InternalConfiguration, scan_directories: bool) -> Result<Self> {
185        log::info!("Creating new Glean v{}", GLEAN_VERSION);
186
187        let application_id = sanitize_application_id(&cfg.application_id);
188        if application_id.is_empty() {
189            return Err(ErrorKind::InvalidConfig.into());
190        }
191
192        let data_path = Path::new(&cfg.data_path);
193        let event_data_store = EventDatabase::new(data_path)?;
194
195        // Create an upload manager with rate limiting of 15 pings every 60 seconds.
196        let mut upload_manager = PingUploadManager::new(&cfg.data_path, &cfg.language_binding_name);
197        let rate_limit = cfg.rate_limit.as_ref().unwrap_or(&PingRateLimit {
198            seconds_per_interval: 60,
199            pings_per_interval: 15,
200        });
201        upload_manager.set_rate_limiter(
202            rate_limit.seconds_per_interval,
203            rate_limit.pings_per_interval,
204        );
205
206        // We only scan the pending ping directories when calling this from a subprocess,
207        // when calling this from ::new we need to scan the directories after dealing with the upload state.
208        if scan_directories {
209            let _scanning_thread = upload_manager.scan_pending_pings_directories(false);
210        }
211
212        let start_time = local_now_with_offset();
213        let mut this = Self {
214            upload_enabled: cfg.upload_enabled,
215            // In the subprocess, we want to avoid accessing the database entirely.
216            // The easiest way to ensure that is to just not initialize it.
217            data_store: None,
218            event_data_store,
219            core_metrics: CoreMetrics::new(),
220            additional_metrics: AdditionalMetrics::new(),
221            database_metrics: DatabaseMetrics::new(),
222            health_metrics: HealthMetrics::new(),
223            internal_pings: InternalPings::new(cfg.enable_internal_pings),
224            upload_manager,
225            data_path: PathBuf::from(&cfg.data_path),
226            application_id,
227            ping_registry: HashMap::new(),
228            start_time,
229            max_events: cfg.max_events.unwrap_or(DEFAULT_MAX_EVENTS),
230            is_first_run: false,
231            debug: DebugOptions::new(),
232            app_build: cfg.app_build.to_string(),
233            // Subprocess doesn't use "metrics" pings so has no need for a scheduler.
234            schedule_metrics_pings: false,
235            remote_settings_epoch: AtomicU8::new(0),
236            remote_settings_config: Arc::new(Mutex::new(RemoteSettingsConfig::new())),
237            with_timestamps: cfg.enable_event_timestamps,
238            ping_schedule: cfg.ping_schedule.clone(),
239        };
240
241        // Ensuring these pings are registered.
242        let pings = this.internal_pings.clone();
243        this.register_ping_type(&pings.baseline);
244        this.register_ping_type(&pings.metrics);
245        this.register_ping_type(&pings.events);
246        this.register_ping_type(&pings.health);
247        this.register_ping_type(&pings.deletion_request);
248
249        Ok(this)
250    }
251
252    /// Creates and initializes a new Glean object.
253    ///
254    /// This will create the necessary directories and files in
255    /// [`cfg.data_path`](InternalConfiguration::data_path). This will also initialize
256    /// the core metrics.
257    pub fn new(cfg: InternalConfiguration) -> Result<Self> {
258        let mut glean = Self::new_for_subprocess(&cfg, false)?;
259
260        // Creating the data store creates the necessary path as well.
261        // If that fails we bail out and don't initialize further.
262        let data_path = Path::new(&cfg.data_path);
263        let ping_lifetime_threshold = cfg.ping_lifetime_threshold as usize;
264        let ping_lifetime_max_time = Duration::from_millis(cfg.ping_lifetime_max_time);
265        glean.data_store = Some(Database::new(
266            data_path,
267            cfg.delay_ping_lifetime_io,
268            ping_lifetime_threshold,
269            ping_lifetime_max_time,
270        )?);
271
272        // Set experimentation identifier (if any)
273        if let Some(experimentation_id) = &cfg.experimentation_id {
274            glean
275                .additional_metrics
276                .experimentation_id
277                .set_sync(&glean, experimentation_id.to_string());
278        }
279
280        // The upload enabled flag may have changed since the last run, for
281        // example by the changing of a config file.
282        if cfg.upload_enabled {
283            // If upload is enabled, just follow the normal code path to
284            // instantiate the core metrics.
285            glean.on_upload_enabled();
286        } else {
287            // If upload is disabled, then clear the metrics
288            // but do not send a deletion request ping.
289            // If we have run before, and we have an old client_id,
290            // do the full upload disabled operations to clear metrics
291            // and send a deletion request ping.
292            match glean
293                .core_metrics
294                .client_id
295                .get_value(&glean, Some("glean_client_info"))
296            {
297                None => glean.clear_metrics(),
298                Some(uuid) => {
299                    if uuid == *KNOWN_CLIENT_ID {
300                        // Previously Glean kept the KNOWN_CLIENT_ID stored.
301                        // Let's ensure we erase it now.
302                        if let Some(data) = glean.data_store.as_ref() {
303                            _ = data.remove_single_metric(
304                                Lifetime::User,
305                                "glean_client_info",
306                                "client_id",
307                            );
308                        }
309                    } else {
310                        // Temporarily enable uploading so we can submit a
311                        // deletion request ping.
312                        glean.upload_enabled = true;
313                        glean.on_upload_disabled(true);
314                    }
315                }
316            }
317        }
318
319        // We set this only for non-subprocess situations.
320        // If internal pings are disabled, we don't set up the MPS either,
321        // it wouldn't send any data anyway.
322        glean.schedule_metrics_pings = cfg.enable_internal_pings && cfg.use_core_mps;
323
324        // We only scan the pendings pings directories **after** dealing with the upload state.
325        // If upload is disabled, we delete all pending pings files
326        // and we need to do that **before** scanning the pending pings folder
327        // to ensure we don't enqueue pings before their files are deleted.
328        let _scanning_thread = glean.upload_manager.scan_pending_pings_directories(true);
329
330        Ok(glean)
331    }
332
333    /// For tests make it easy to create a Glean object using only the required configuration.
334    #[cfg(test)]
335    pub(crate) fn with_options(
336        data_path: &str,
337        application_id: &str,
338        upload_enabled: bool,
339        enable_internal_pings: bool,
340    ) -> Self {
341        let cfg = InternalConfiguration {
342            data_path: data_path.into(),
343            application_id: application_id.into(),
344            language_binding_name: "Rust".into(),
345            upload_enabled,
346            max_events: None,
347            delay_ping_lifetime_io: false,
348            app_build: "Unknown".into(),
349            use_core_mps: false,
350            trim_data_to_registered_pings: false,
351            log_level: None,
352            rate_limit: None,
353            enable_event_timestamps: true,
354            experimentation_id: None,
355            enable_internal_pings,
356            ping_schedule: Default::default(),
357            ping_lifetime_threshold: 0,
358            ping_lifetime_max_time: 0,
359        };
360
361        let mut glean = Self::new(cfg).unwrap();
362
363        // Disable all upload manager policies for testing
364        glean.upload_manager = PingUploadManager::no_policy(data_path);
365
366        glean
367    }
368
369    /// Destroys the database.
370    ///
371    /// After this Glean needs to be reinitialized.
372    pub fn destroy_db(&mut self) {
373        self.data_store = None;
374    }
375
376    /// Initializes the core metrics managed by Glean's Rust core.
377    fn initialize_core_metrics(&mut self) {
378        let need_new_client_id = match self
379            .core_metrics
380            .client_id
381            .get_value(self, Some("glean_client_info"))
382        {
383            None => true,
384            Some(uuid) => uuid == *KNOWN_CLIENT_ID,
385        };
386        if need_new_client_id {
387            self.core_metrics.client_id.generate_and_set_sync(self);
388        }
389
390        if self
391            .core_metrics
392            .first_run_date
393            .get_value(self, "glean_client_info")
394            .is_none()
395        {
396            self.core_metrics.first_run_date.set_sync(self, None);
397            // The `first_run_date` field is generated on the very first run
398            // and persisted across upload toggling. We can assume that, the only
399            // time it is set, that's indeed our "first run".
400            self.is_first_run = true;
401        }
402
403        self.set_application_lifetime_core_metrics();
404    }
405
406    /// Initializes the database metrics managed by Glean's Rust core.
407    fn initialize_database_metrics(&mut self) {
408        log::trace!("Initializing database metrics");
409
410        if let Some(size) = self
411            .data_store
412            .as_ref()
413            .and_then(|database| database.file_size())
414        {
415            log::trace!("Database file size: {}", size.get());
416            self.database_metrics
417                .size
418                .accumulate_sync(self, size.get() as i64)
419        }
420
421        if let Some(rkv_load_state) = self
422            .data_store
423            .as_ref()
424            .and_then(|database| database.rkv_load_state())
425        {
426            self.database_metrics
427                .rkv_load_error
428                .set_sync(self, rkv_load_state)
429        }
430
431        if let Some(load_sizes) = self
432            .data_store
433            .as_mut()
434            .and_then(|database| database.load_sizes())
435        {
436            self.database_metrics.load_sizes.set_sync(
437                self,
438                serde_json::to_value(load_sizes).unwrap_or(serde_json::json!({})),
439            );
440        }
441    }
442
443    /// Signals that the environment is ready to submit pings.
444    ///
445    /// Should be called when Glean is initialized to the point where it can correctly assemble pings.
446    /// Usually called from the language binding after all of the core metrics have been set
447    /// and the ping types have been registered.
448    ///
449    /// # Arguments
450    ///
451    /// * `trim_data_to_registered_pings` - Whether we should limit to storing data only for
452    ///   data belonging to pings previously registered via `register_ping_type`.
453    ///
454    /// # Returns
455    ///
456    /// Whether the "events" ping was submitted.
457    pub fn on_ready_to_submit_pings(&mut self, trim_data_to_registered_pings: bool) -> bool {
458        // When upload is disabled on init we already clear out metrics.
459        // However at that point not all pings are registered and so we keep that data around.
460        // By the time we would be ready to submit we try again cleaning out metrics from
461        // now-known pings.
462        if !self.upload_enabled {
463            log::debug!("on_ready_to_submit_pings. let's clear pings once again.");
464            self.clear_metrics();
465        }
466
467        self.event_data_store
468            .flush_pending_events_on_startup(self, trim_data_to_registered_pings)
469    }
470
471    /// Sets whether upload is enabled or not.
472    ///
473    /// When uploading is disabled, metrics aren't recorded at all and no
474    /// data is uploaded.
475    ///
476    /// When disabling, all pending metrics, events and queued pings are cleared.
477    ///
478    /// When enabling, the core Glean metrics are recreated.
479    ///
480    /// If the value of this flag is not actually changed, this is a no-op.
481    ///
482    /// # Arguments
483    ///
484    /// * `flag` - When true, enable metric collection.
485    ///
486    /// # Returns
487    ///
488    /// Whether the flag was different from the current value,
489    /// and actual work was done to clear or reinstate metrics.
490    pub fn set_upload_enabled(&mut self, flag: bool) -> bool {
491        log::info!("Upload enabled: {:?}", flag);
492
493        if self.upload_enabled != flag {
494            if flag {
495                self.on_upload_enabled();
496            } else {
497                self.on_upload_disabled(false);
498            }
499            true
500        } else {
501            false
502        }
503    }
504
505    /// Enable or disable a ping.
506    ///
507    /// Disabling a ping causes all data for that ping to be removed from storage
508    /// and all pending pings of that type to be deleted.
509    ///
510    /// **Note**: Do not use directly. Call `PingType::set_enabled` instead.
511    #[doc(hidden)]
512    pub fn set_ping_enabled(&mut self, ping: &PingType, enabled: bool) {
513        ping.store_enabled(enabled);
514        if !enabled {
515            if let Some(data) = self.data_store.as_ref() {
516                _ = data.clear_ping_lifetime_storage(ping.name());
517                _ = data.clear_lifetime_storage(Lifetime::User, ping.name());
518                _ = data.clear_lifetime_storage(Lifetime::Application, ping.name());
519            }
520            let ping_maker = PingMaker::new();
521            let disabled_pings = &[ping.name()][..];
522            if let Err(err) = ping_maker.clear_pending_pings(self.get_data_path(), disabled_pings) {
523                log::warn!("Error clearing pending pings: {}", err);
524            }
525        }
526    }
527
528    /// Determines whether upload is enabled.
529    ///
530    /// When upload is disabled, no data will be recorded.
531    pub fn is_upload_enabled(&self) -> bool {
532        self.upload_enabled
533    }
534
535    /// Check if a ping is enabled.
536    ///
537    /// Note that some internal "ping" names are considered to be always enabled.
538    ///
539    /// If a ping is not known to Glean ("unregistered") it is always considered disabled.
540    /// If a ping is known, it can be enabled/disabled at any point.
541    /// Only data for enabled pings is recorded.
542    /// Disabled pings are never submitted.
543    pub fn is_ping_enabled(&self, ping: &str) -> bool {
544        // We "abuse" pings/storage names for internal data.
545        const DEFAULT_ENABLED: &[&str] = &[
546            "glean_client_info",
547            "glean_internal_info",
548            // for `experimentation_id`.
549            // That should probably have gone into `glean_internal_info` instead.
550            "all-pings",
551        ];
552
553        // `client_info`-like stuff is always enabled.
554        if DEFAULT_ENABLED.contains(&ping) {
555            return true;
556        }
557
558        let Some(ping) = self.ping_registry.get(ping) else {
559            log::trace!("Unknown ping {ping}. Assuming disabled.");
560            return false;
561        };
562
563        ping.enabled(self)
564    }
565
566    /// Handles the changing of state from upload disabled to enabled.
567    ///
568    /// Should only be called when the state actually changes.
569    ///
570    /// The `upload_enabled` flag is set to true and the core Glean metrics are
571    /// recreated.
572    fn on_upload_enabled(&mut self) {
573        self.upload_enabled = true;
574        self.initialize_core_metrics();
575        self.initialize_database_metrics();
576    }
577
578    /// Handles the changing of state from upload enabled to disabled.
579    ///
580    /// Should only be called when the state actually changes.
581    ///
582    /// A deletion_request ping is sent, all pending metrics, events and queued
583    /// pings are cleared, and the client_id is set to KNOWN_CLIENT_ID.
584    /// Afterward, the upload_enabled flag is set to false.
585    fn on_upload_disabled(&mut self, during_init: bool) {
586        // The upload_enabled flag should be true here, or the deletion ping
587        // won't be submitted.
588        let reason = if during_init {
589            Some("at_init")
590        } else {
591            Some("set_upload_enabled")
592        };
593        if !self
594            .internal_pings
595            .deletion_request
596            .submit_sync(self, reason)
597        {
598            log::error!("Failed to submit deletion-request ping on optout.");
599        }
600        self.clear_metrics();
601        self.upload_enabled = false;
602    }
603
604    /// Clear any pending metrics when telemetry is disabled.
605    fn clear_metrics(&mut self) {
606        // Clear the pending pings queue and acquire the lock
607        // so that it can't be accessed until this function is done.
608        let _lock = self.upload_manager.clear_ping_queue();
609
610        // Clear any pending pings that follow `collection_enabled`.
611        let ping_maker = PingMaker::new();
612        let disabled_pings = self
613            .ping_registry
614            .iter()
615            .filter(|&(_ping_name, ping)| ping.follows_collection_enabled())
616            .map(|(ping_name, _ping)| &ping_name[..])
617            .collect::<Vec<_>>();
618        if let Err(err) = ping_maker.clear_pending_pings(self.get_data_path(), &disabled_pings) {
619            log::warn!("Error clearing pending pings: {}", err);
620        }
621
622        // Delete all stored metrics.
623        // Note that this also includes the ping sequence numbers, so it has
624        // the effect of resetting those to their initial values.
625        if let Some(data) = self.data_store.as_ref() {
626            _ = data.clear_lifetime_storage(Lifetime::User, "glean_internal_info");
627            _ = data.remove_single_metric(Lifetime::User, "glean_client_info", "client_id");
628            for (ping_name, ping) in &self.ping_registry {
629                if ping.follows_collection_enabled() {
630                    _ = data.clear_ping_lifetime_storage(ping_name);
631                    _ = data.clear_lifetime_storage(Lifetime::User, ping_name);
632                    _ = data.clear_lifetime_storage(Lifetime::Application, ping_name);
633                }
634            }
635        }
636        if let Err(err) = self.event_data_store.clear_all() {
637            log::warn!("Error clearing pending events: {}", err);
638        }
639
640        // This does not clear the experiments store (which isn't managed by the
641        // StorageEngineManager), since doing so would mean we would have to have the
642        // application tell us again which experiments are active if telemetry is
643        // re-enabled.
644    }
645
646    /// Gets the application ID as specified on instantiation.
647    pub fn get_application_id(&self) -> &str {
648        &self.application_id
649    }
650
651    /// Gets the data path of this instance.
652    pub fn get_data_path(&self) -> &Path {
653        &self.data_path
654    }
655
656    /// Gets a handle to the database.
657    #[track_caller] // If this fails we're interested in the caller.
658    pub fn storage(&self) -> &Database {
659        self.data_store.as_ref().expect("No database found")
660    }
661
662    /// Gets an optional handle to the database.
663    pub fn storage_opt(&self) -> Option<&Database> {
664        self.data_store.as_ref()
665    }
666
667    /// Gets a handle to the event database.
668    pub fn event_storage(&self) -> &EventDatabase {
669        &self.event_data_store
670    }
671
672    pub(crate) fn with_timestamps(&self) -> bool {
673        self.with_timestamps
674    }
675
676    /// Gets the maximum number of events to store before sending a ping.
677    pub fn get_max_events(&self) -> usize {
678        let remote_settings_config = self.remote_settings_config.lock().unwrap();
679
680        if let Some(max_events) = remote_settings_config.event_threshold {
681            max_events as usize
682        } else {
683            self.max_events as usize
684        }
685    }
686
687    /// Gets the next task for an uploader.
688    ///
689    /// This can be one of:
690    ///
691    /// * [`Wait`](PingUploadTask::Wait) - which means the requester should ask
692    ///   again later;
693    /// * [`Upload(PingRequest)`](PingUploadTask::Upload) - which means there is
694    ///   a ping to upload. This wraps the actual request object;
695    /// * [`Done`](PingUploadTask::Done) - which means requester should stop
696    ///   asking for now.
697    ///
698    /// # Returns
699    ///
700    /// A [`PingUploadTask`] representing the next task.
701    pub fn get_upload_task(&self) -> PingUploadTask {
702        self.upload_manager.get_upload_task(self, self.log_pings())
703    }
704
705    /// Processes the response from an attempt to upload a ping.
706    ///
707    /// # Arguments
708    ///
709    /// * `uuid` - The UUID of the ping in question.
710    /// * `status` - The upload result.
711    pub fn process_ping_upload_response(
712        &self,
713        uuid: &str,
714        status: UploadResult,
715    ) -> UploadTaskAction {
716        self.upload_manager
717            .process_ping_upload_response(self, uuid, status)
718    }
719
720    /// Takes a snapshot for the given store and optionally clear it.
721    ///
722    /// # Arguments
723    ///
724    /// * `store_name` - The store to snapshot.
725    /// * `clear_store` - Whether to clear the store after snapshotting.
726    ///
727    /// # Returns
728    ///
729    /// The snapshot in a string encoded as JSON. If the snapshot is empty, returns an empty string.
730    pub fn snapshot(&mut self, store_name: &str, clear_store: bool) -> String {
731        StorageManager
732            .snapshot(self.storage(), store_name, clear_store)
733            .unwrap_or_else(|| String::from(""))
734    }
735
736    pub(crate) fn make_path(&self, ping_name: &str, doc_id: &str) -> String {
737        format!(
738            "/submit/{}/{}/{}/{}",
739            self.get_application_id(),
740            ping_name,
741            GLEAN_SCHEMA_VERSION,
742            doc_id
743        )
744    }
745
746    /// Collects and submits a ping by name for eventual uploading.
747    ///
748    /// The ping content is assembled as soon as possible, but upload is not
749    /// guaranteed to happen immediately, as that depends on the upload policies.
750    ///
751    /// If the ping currently contains no content, it will not be sent,
752    /// unless it is configured to be sent if empty.
753    ///
754    /// # Arguments
755    ///
756    /// * `ping_name` - The name of the ping to submit
757    /// * `reason` - A reason code to include in the ping
758    ///
759    /// # Returns
760    ///
761    /// Whether the ping was succesfully assembled and queued.
762    ///
763    /// # Errors
764    ///
765    /// If collecting or writing the ping to disk failed.
766    pub fn submit_ping_by_name(&self, ping_name: &str, reason: Option<&str>) -> bool {
767        match self.get_ping_by_name(ping_name) {
768            None => {
769                log::error!("Attempted to submit unknown ping '{}'", ping_name);
770                false
771            }
772            Some(ping) => ping.submit_sync(self, reason),
773        }
774    }
775
776    /// Gets a [`PingType`] by name.
777    ///
778    /// # Returns
779    ///
780    /// The [`PingType`] of a ping if the given name was registered before, [`None`]
781    /// otherwise.
782    pub fn get_ping_by_name(&self, ping_name: &str) -> Option<&PingType> {
783        self.ping_registry.get(ping_name)
784    }
785
786    /// Register a new [`PingType`](metrics/struct.PingType.html).
787    pub fn register_ping_type(&mut self, ping: &PingType) {
788        if self.ping_registry.contains_key(ping.name()) {
789            log::debug!("Duplicate ping named '{}'", ping.name())
790        }
791
792        self.ping_registry
793            .insert(ping.name().to_string(), ping.clone());
794    }
795
796    /// Gets a list of currently registered ping names.
797    ///
798    /// # Returns
799    ///
800    /// The list of ping names that are currently registered.
801    pub fn get_registered_ping_names(&self) -> Vec<&str> {
802        self.ping_registry.keys().map(String::as_str).collect()
803    }
804
805    /// Get create time of the Glean object.
806    pub(crate) fn start_time(&self) -> DateTime<FixedOffset> {
807        self.start_time
808    }
809
810    /// Indicates that an experiment is running.
811    ///
812    /// Glean will then add an experiment annotation to the environment
813    /// which is sent with pings. This information is not persisted between runs.
814    ///
815    /// # Arguments
816    ///
817    /// * `experiment_id` - The id of the active experiment (maximum 30 bytes).
818    /// * `branch` - The experiment branch (maximum 30 bytes).
819    /// * `extra` - Optional metadata to output with the ping.
820    pub fn set_experiment_active(
821        &self,
822        experiment_id: String,
823        branch: String,
824        extra: HashMap<String, String>,
825    ) {
826        let metric = ExperimentMetric::new(self, experiment_id);
827        metric.set_active_sync(self, branch, extra);
828    }
829
830    /// Indicates that an experiment is no longer running.
831    ///
832    /// # Arguments
833    ///
834    /// * `experiment_id` - The id of the active experiment to deactivate (maximum 30 bytes).
835    pub fn set_experiment_inactive(&self, experiment_id: String) {
836        let metric = ExperimentMetric::new(self, experiment_id);
837        metric.set_inactive_sync(self);
838    }
839
840    /// **Test-only API (exported for FFI purposes).**
841    ///
842    /// Gets stored data for the requested experiment.
843    ///
844    /// # Arguments
845    ///
846    /// * `experiment_id` - The id of the active experiment (maximum 30 bytes).
847    pub fn test_get_experiment_data(&self, experiment_id: String) -> Option<RecordedExperiment> {
848        let metric = ExperimentMetric::new(self, experiment_id);
849        metric.test_get_value(self)
850    }
851
852    /// **Test-only API (exported for FFI purposes).**
853    ///
854    /// Gets stored experimentation id annotation.
855    pub fn test_get_experimentation_id(&self) -> Option<String> {
856        self.additional_metrics
857            .experimentation_id
858            .get_value(self, None)
859    }
860
861    /// Set configuration to override the default state, typically initiated from a
862    /// remote_settings experiment or rollout
863    ///
864    /// # Arguments
865    ///
866    /// * `cfg` - The stringified JSON representation of a `RemoteSettingsConfig` object
867    pub fn apply_server_knobs_config(&self, cfg: RemoteSettingsConfig) {
868        // Set the current RemoteSettingsConfig, keeping the lock until the epoch is
869        // updated to prevent against reading a "new" config but an "old" epoch
870        let mut remote_settings_config = self.remote_settings_config.lock().unwrap();
871
872        // Merge the exising metrics configuration with the supplied one
873        remote_settings_config
874            .metrics_enabled
875            .extend(cfg.metrics_enabled);
876
877        // Merge the exising ping configuration with the supplied one
878        remote_settings_config
879            .pings_enabled
880            .extend(cfg.pings_enabled);
881
882        remote_settings_config.event_threshold = cfg.event_threshold;
883
884        // Update remote_settings epoch
885        self.remote_settings_epoch.fetch_add(1, Ordering::SeqCst);
886    }
887
888    /// Persists [`Lifetime::Ping`] data that might be in memory in case
889    /// [`delay_ping_lifetime_io`](InternalConfiguration::delay_ping_lifetime_io) is set
890    /// or was set at a previous time.
891    ///
892    /// If there is no data to persist, this function does nothing.
893    pub fn persist_ping_lifetime_data(&self) -> Result<()> {
894        if let Some(data) = self.data_store.as_ref() {
895            return data.persist_ping_lifetime_data();
896        }
897
898        Ok(())
899    }
900
901    /// Sets internally-handled application lifetime metrics.
902    fn set_application_lifetime_core_metrics(&self) {
903        self.core_metrics.os.set_sync(self, system::OS);
904    }
905
906    /// **This is not meant to be used directly.**
907    ///
908    /// Clears all the metrics that have [`Lifetime::Application`].
909    pub fn clear_application_lifetime_metrics(&self) {
910        log::trace!("Clearing Lifetime::Application metrics");
911        if let Some(data) = self.data_store.as_ref() {
912            data.clear_lifetime(Lifetime::Application);
913        }
914
915        // Set internally handled app lifetime metrics again.
916        self.set_application_lifetime_core_metrics();
917    }
918
919    /// Whether or not this is the first run on this profile.
920    pub fn is_first_run(&self) -> bool {
921        self.is_first_run
922    }
923
924    /// Sets a debug view tag.
925    ///
926    /// This will return `false` in case `value` is not a valid tag.
927    ///
928    /// When the debug view tag is set, pings are sent with a `X-Debug-ID` header with the value of the tag
929    /// and are sent to the ["Ping Debug Viewer"](https://mozilla.github.io/glean/book/dev/core/internal/debug-pings.html).
930    ///
931    /// # Arguments
932    ///
933    /// * `value` - A valid HTTP header value. Must match the regex: "[a-zA-Z0-9-]{1,20}".
934    pub fn set_debug_view_tag(&mut self, value: &str) -> bool {
935        self.debug.debug_view_tag.set(value.into())
936    }
937
938    /// Return the value for the debug view tag or [`None`] if it hasn't been set.
939    ///
940    /// The `debug_view_tag` may be set from an environment variable
941    /// (`GLEAN_DEBUG_VIEW_TAG`) or through the `set_debug_view_tag` function.
942    pub fn debug_view_tag(&self) -> Option<&String> {
943        self.debug.debug_view_tag.get()
944    }
945
946    /// Sets source tags.
947    ///
948    /// This will return `false` in case `value` contains invalid tags.
949    ///
950    /// Ping tags will show in the destination datasets, after ingestion.
951    ///
952    /// **Note** If one or more tags are invalid, all tags are ignored.
953    ///
954    /// # Arguments
955    ///
956    /// * `value` - A vector of at most 5 valid HTTP header values. Individual tags must match the regex: "[a-zA-Z0-9-]{1,20}".
957    pub fn set_source_tags(&mut self, value: Vec<String>) -> bool {
958        self.debug.source_tags.set(value)
959    }
960
961    /// Return the value for the source tags or [`None`] if it hasn't been set.
962    ///
963    /// The `source_tags` may be set from an environment variable (`GLEAN_SOURCE_TAGS`)
964    /// or through the [`set_source_tags`] function.
965    pub(crate) fn source_tags(&self) -> Option<&Vec<String>> {
966        self.debug.source_tags.get()
967    }
968
969    /// Sets the log pings debug option.
970    ///
971    /// This will return `false` in case we are unable to set the option.
972    ///
973    /// When the log pings debug option is `true`,
974    /// we log the payload of all succesfully assembled pings.
975    ///
976    /// # Arguments
977    ///
978    /// * `value` - The value of the log pings option
979    pub fn set_log_pings(&mut self, value: bool) -> bool {
980        self.debug.log_pings.set(value)
981    }
982
983    /// Return the value for the log pings debug option or `false` if it hasn't been set.
984    ///
985    /// The `log_pings` option may be set from an environment variable (`GLEAN_LOG_PINGS`)
986    /// or through the `set_log_pings` function.
987    pub fn log_pings(&self) -> bool {
988        self.debug.log_pings.get().copied().unwrap_or(false)
989    }
990
991    fn get_dirty_bit_metric(&self) -> metrics::BooleanMetric {
992        metrics::BooleanMetric::new(CommonMetricData {
993            name: "dirtybit".into(),
994            // We don't need a category, the name is already unique
995            category: "".into(),
996            send_in_pings: vec![INTERNAL_STORAGE.into()],
997            lifetime: Lifetime::User,
998            ..Default::default()
999        })
1000    }
1001
1002    /// **This is not meant to be used directly.**
1003    ///
1004    /// Sets the value of a "dirty flag" in the permanent storage.
1005    ///
1006    /// The "dirty flag" is meant to have the following behaviour, implemented
1007    /// by the consumers of the FFI layer:
1008    ///
1009    /// - on mobile: set to `false` when going to background or shutting down,
1010    ///   set to `true` at startup and when going to foreground.
1011    /// - on non-mobile platforms: set to `true` at startup and `false` at
1012    ///   shutdown.
1013    ///
1014    /// At startup, before setting its new value, if the "dirty flag" value is
1015    /// `true`, then Glean knows it did not exit cleanly and can implement
1016    /// coping mechanisms (e.g. sending a `baseline` ping).
1017    pub fn set_dirty_flag(&self, new_value: bool) {
1018        self.get_dirty_bit_metric().set_sync(self, new_value);
1019    }
1020
1021    /// **This is not meant to be used directly.**
1022    ///
1023    /// Checks the stored value of the "dirty flag".
1024    pub fn is_dirty_flag_set(&self) -> bool {
1025        let dirty_bit_metric = self.get_dirty_bit_metric();
1026        match StorageManager.snapshot_metric(
1027            self.storage(),
1028            INTERNAL_STORAGE,
1029            &dirty_bit_metric.meta().identifier(self),
1030            dirty_bit_metric.meta().inner.lifetime,
1031        ) {
1032            Some(Metric::Boolean(b)) => b,
1033            _ => false,
1034        }
1035    }
1036
1037    /// Performs the collection/cleanup operations required by becoming active.
1038    ///
1039    /// This functions generates a baseline ping with reason `active`
1040    /// and then sets the dirty bit.
1041    pub fn handle_client_active(&mut self) {
1042        if !self
1043            .internal_pings
1044            .baseline
1045            .submit_sync(self, Some("active"))
1046        {
1047            log::info!("baseline ping not submitted on active");
1048        }
1049
1050        self.set_dirty_flag(true);
1051    }
1052
1053    /// Performs the collection/cleanup operations required by becoming inactive.
1054    ///
1055    /// This functions generates a baseline and an events ping with reason
1056    /// `inactive` and then clears the dirty bit.
1057    pub fn handle_client_inactive(&mut self) {
1058        if !self
1059            .internal_pings
1060            .baseline
1061            .submit_sync(self, Some("inactive"))
1062        {
1063            log::info!("baseline ping not submitted on inactive");
1064        }
1065
1066        if !self
1067            .internal_pings
1068            .events
1069            .submit_sync(self, Some("inactive"))
1070        {
1071            log::info!("events ping not submitted on inactive");
1072        }
1073
1074        self.set_dirty_flag(false);
1075    }
1076
1077    /// **Test-only API (exported for FFI purposes).**
1078    ///
1079    /// Deletes all stored metrics.
1080    ///
1081    /// Note that this also includes the ping sequence numbers, so it has
1082    /// the effect of resetting those to their initial values.
1083    pub fn test_clear_all_stores(&self) {
1084        if let Some(data) = self.data_store.as_ref() {
1085            data.clear_all()
1086        }
1087        // We don't care about this failing, maybe the data does just not exist.
1088        let _ = self.event_data_store.clear_all();
1089    }
1090
1091    /// Instructs the Metrics Ping Scheduler's thread to exit cleanly.
1092    /// If Glean was configured with `use_core_mps: false`, this has no effect.
1093    pub fn cancel_metrics_ping_scheduler(&self) {
1094        if self.schedule_metrics_pings {
1095            scheduler::cancel();
1096        }
1097    }
1098
1099    /// Instructs the Metrics Ping Scheduler to being scheduling metrics pings.
1100    /// If Glean wsa configured with `use_core_mps: false`, this has no effect.
1101    pub fn start_metrics_ping_scheduler(&self) {
1102        if self.schedule_metrics_pings {
1103            scheduler::schedule(self);
1104        }
1105    }
1106
1107    /// Updates attribution fields with new values.
1108    /// AttributionMetrics fields with `None` values will not overwrite older values.
1109    pub fn update_attribution(&self, attribution: AttributionMetrics) {
1110        if let Some(source) = attribution.source {
1111            self.core_metrics.attribution_source.set_sync(self, source);
1112        }
1113        if let Some(medium) = attribution.medium {
1114            self.core_metrics.attribution_medium.set_sync(self, medium);
1115        }
1116        if let Some(campaign) = attribution.campaign {
1117            self.core_metrics
1118                .attribution_campaign
1119                .set_sync(self, campaign);
1120        }
1121        if let Some(term) = attribution.term {
1122            self.core_metrics.attribution_term.set_sync(self, term);
1123        }
1124        if let Some(content) = attribution.content {
1125            self.core_metrics
1126                .attribution_content
1127                .set_sync(self, content);
1128        }
1129    }
1130
1131    /// **TEST-ONLY Method**
1132    ///
1133    /// Returns the current attribution metrics.
1134    pub fn test_get_attribution(&self) -> AttributionMetrics {
1135        AttributionMetrics {
1136            source: self
1137                .core_metrics
1138                .attribution_source
1139                .get_value(self, Some("glean_client_info")),
1140            medium: self
1141                .core_metrics
1142                .attribution_medium
1143                .get_value(self, Some("glean_client_info")),
1144            campaign: self
1145                .core_metrics
1146                .attribution_campaign
1147                .get_value(self, Some("glean_client_info")),
1148            term: self
1149                .core_metrics
1150                .attribution_term
1151                .get_value(self, Some("glean_client_info")),
1152            content: self
1153                .core_metrics
1154                .attribution_content
1155                .get_value(self, Some("glean_client_info")),
1156        }
1157    }
1158
1159    /// Updates distribution fields with new values.
1160    /// DistributionMetrics fields with `None` values will not overwrite older values.
1161    pub fn update_distribution(&self, distribution: DistributionMetrics) {
1162        if let Some(name) = distribution.name {
1163            self.core_metrics.distribution_name.set_sync(self, name);
1164        }
1165    }
1166
1167    /// **TEST-ONLY Method**
1168    ///
1169    /// Returns the current distribution metrics.
1170    pub fn test_get_distribution(&self) -> DistributionMetrics {
1171        DistributionMetrics {
1172            name: self
1173                .core_metrics
1174                .distribution_name
1175                .get_value(self, Some("glean_client_info")),
1176        }
1177    }
1178}