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