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
432 /// Signals that the environment is ready to submit pings.
433 ///
434 /// Should be called when Glean is initialized to the point where it can correctly assemble pings.
435 /// Usually called from the language binding after all of the core metrics have been set
436 /// and the ping types have been registered.
437 ///
438 /// # Arguments
439 ///
440 /// * `trim_data_to_registered_pings` - Whether we should limit to storing data only for
441 /// data belonging to pings previously registered via `register_ping_type`.
442 ///
443 /// # Returns
444 ///
445 /// Whether the "events" ping was submitted.
446 pub fn on_ready_to_submit_pings(&mut self, trim_data_to_registered_pings: bool) -> bool {
447 // When upload is disabled on init we already clear out metrics.
448 // However at that point not all pings are registered and so we keep that data around.
449 // By the time we would be ready to submit we try again cleaning out metrics from
450 // now-known pings.
451 if !self.upload_enabled {
452 log::debug!("on_ready_to_submit_pings. let's clear pings once again.");
453 self.clear_metrics();
454 }
455
456 self.event_data_store
457 .flush_pending_events_on_startup(self, trim_data_to_registered_pings)
458 }
459
460 /// Sets whether upload is enabled or not.
461 ///
462 /// When uploading is disabled, metrics aren't recorded at all and no
463 /// data is uploaded.
464 ///
465 /// When disabling, all pending metrics, events and queued pings are cleared.
466 ///
467 /// When enabling, the core Glean metrics are recreated.
468 ///
469 /// If the value of this flag is not actually changed, this is a no-op.
470 ///
471 /// # Arguments
472 ///
473 /// * `flag` - When true, enable metric collection.
474 ///
475 /// # Returns
476 ///
477 /// Whether the flag was different from the current value,
478 /// and actual work was done to clear or reinstate metrics.
479 pub fn set_upload_enabled(&mut self, flag: bool) -> bool {
480 log::info!("Upload enabled: {:?}", flag);
481
482 if self.upload_enabled != flag {
483 if flag {
484 self.on_upload_enabled();
485 } else {
486 self.on_upload_disabled(false);
487 }
488 true
489 } else {
490 false
491 }
492 }
493
494 /// Enable or disable a ping.
495 ///
496 /// Disabling a ping causes all data for that ping to be removed from storage
497 /// and all pending pings of that type to be deleted.
498 ///
499 /// **Note**: Do not use directly. Call `PingType::set_enabled` instead.
500 #[doc(hidden)]
501 pub fn set_ping_enabled(&mut self, ping: &PingType, enabled: bool) {
502 ping.store_enabled(enabled);
503 if !enabled {
504 if let Some(data) = self.data_store.as_ref() {
505 _ = data.clear_ping_lifetime_storage(ping.name());
506 _ = data.clear_lifetime_storage(Lifetime::User, ping.name());
507 _ = data.clear_lifetime_storage(Lifetime::Application, ping.name());
508 }
509 let ping_maker = PingMaker::new();
510 let disabled_pings = &[ping.name()][..];
511 if let Err(err) = ping_maker.clear_pending_pings(self.get_data_path(), disabled_pings) {
512 log::warn!("Error clearing pending pings: {}", err);
513 }
514 }
515 }
516
517 /// Determines whether upload is enabled.
518 ///
519 /// When upload is disabled, no data will be recorded.
520 pub fn is_upload_enabled(&self) -> bool {
521 self.upload_enabled
522 }
523
524 /// Check if a ping is enabled.
525 ///
526 /// Note that some internal "ping" names are considered to be always enabled.
527 ///
528 /// If a ping is not known to Glean ("unregistered") it is always considered disabled.
529 /// If a ping is known, it can be enabled/disabled at any point.
530 /// Only data for enabled pings is recorded.
531 /// Disabled pings are never submitted.
532 pub fn is_ping_enabled(&self, ping: &str) -> bool {
533 // We "abuse" pings/storage names for internal data.
534 const DEFAULT_ENABLED: &[&str] = &[
535 "glean_client_info",
536 "glean_internal_info",
537 // for `experimentation_id`.
538 // That should probably have gone into `glean_internal_info` instead.
539 "all-pings",
540 ];
541
542 // `client_info`-like stuff is always enabled.
543 if DEFAULT_ENABLED.contains(&ping) {
544 return true;
545 }
546
547 let Some(ping) = self.ping_registry.get(ping) else {
548 log::trace!("Unknown ping {ping}. Assuming disabled.");
549 return false;
550 };
551
552 ping.enabled(self)
553 }
554
555 /// Handles the changing of state from upload disabled to enabled.
556 ///
557 /// Should only be called when the state actually changes.
558 ///
559 /// The `upload_enabled` flag is set to true and the core Glean metrics are
560 /// recreated.
561 fn on_upload_enabled(&mut self) {
562 self.upload_enabled = true;
563 self.initialize_core_metrics();
564 self.initialize_database_metrics();
565 }
566
567 /// Handles the changing of state from upload enabled to disabled.
568 ///
569 /// Should only be called when the state actually changes.
570 ///
571 /// A deletion_request ping is sent, all pending metrics, events and queued
572 /// pings are cleared, and the client_id is set to KNOWN_CLIENT_ID.
573 /// Afterward, the upload_enabled flag is set to false.
574 fn on_upload_disabled(&mut self, during_init: bool) {
575 // The upload_enabled flag should be true here, or the deletion ping
576 // won't be submitted.
577 let reason = if during_init {
578 Some("at_init")
579 } else {
580 Some("set_upload_enabled")
581 };
582 if !self
583 .internal_pings
584 .deletion_request
585 .submit_sync(self, reason)
586 {
587 log::error!("Failed to submit deletion-request ping on optout.");
588 }
589 self.clear_metrics();
590 self.upload_enabled = false;
591 }
592
593 /// Clear any pending metrics when telemetry is disabled.
594 fn clear_metrics(&mut self) {
595 // Clear the pending pings queue and acquire the lock
596 // so that it can't be accessed until this function is done.
597 let _lock = self.upload_manager.clear_ping_queue();
598
599 // Clear any pending pings that follow `collection_enabled`.
600 let ping_maker = PingMaker::new();
601 let disabled_pings = self
602 .ping_registry
603 .iter()
604 .filter(|&(_ping_name, ping)| ping.follows_collection_enabled())
605 .map(|(ping_name, _ping)| &ping_name[..])
606 .collect::<Vec<_>>();
607 if let Err(err) = ping_maker.clear_pending_pings(self.get_data_path(), &disabled_pings) {
608 log::warn!("Error clearing pending pings: {}", err);
609 }
610
611 // Delete all stored metrics.
612 // Note that this also includes the ping sequence numbers, so it has
613 // the effect of resetting those to their initial values.
614 if let Some(data) = self.data_store.as_ref() {
615 _ = data.clear_lifetime_storage(Lifetime::User, "glean_internal_info");
616 _ = data.remove_single_metric(Lifetime::User, "glean_client_info", "client_id");
617 for (ping_name, ping) in &self.ping_registry {
618 if ping.follows_collection_enabled() {
619 _ = data.clear_ping_lifetime_storage(ping_name);
620 _ = data.clear_lifetime_storage(Lifetime::User, ping_name);
621 _ = data.clear_lifetime_storage(Lifetime::Application, ping_name);
622 }
623 }
624 }
625 if let Err(err) = self.event_data_store.clear_all() {
626 log::warn!("Error clearing pending events: {}", err);
627 }
628
629 // This does not clear the experiments store (which isn't managed by the
630 // StorageEngineManager), since doing so would mean we would have to have the
631 // application tell us again which experiments are active if telemetry is
632 // re-enabled.
633 }
634
635 /// Gets the application ID as specified on instantiation.
636 pub fn get_application_id(&self) -> &str {
637 &self.application_id
638 }
639
640 /// Gets the data path of this instance.
641 pub fn get_data_path(&self) -> &Path {
642 &self.data_path
643 }
644
645 /// Gets a handle to the database.
646 #[track_caller] // If this fails we're interested in the caller.
647 pub fn storage(&self) -> &Database {
648 self.data_store.as_ref().expect("No database found")
649 }
650
651 /// Gets an optional handle to the database.
652 pub fn storage_opt(&self) -> Option<&Database> {
653 self.data_store.as_ref()
654 }
655
656 /// Gets a handle to the event database.
657 pub fn event_storage(&self) -> &EventDatabase {
658 &self.event_data_store
659 }
660
661 pub(crate) fn with_timestamps(&self) -> bool {
662 self.with_timestamps
663 }
664
665 /// Gets the maximum number of events to store before sending a ping.
666 pub fn get_max_events(&self) -> usize {
667 let remote_settings_config = self.remote_settings_config.lock().unwrap();
668
669 if let Some(max_events) = remote_settings_config.event_threshold {
670 max_events as usize
671 } else {
672 self.max_events as usize
673 }
674 }
675
676 /// Gets the next task for an uploader.
677 ///
678 /// This can be one of:
679 ///
680 /// * [`Wait`](PingUploadTask::Wait) - which means the requester should ask
681 /// again later;
682 /// * [`Upload(PingRequest)`](PingUploadTask::Upload) - which means there is
683 /// a ping to upload. This wraps the actual request object;
684 /// * [`Done`](PingUploadTask::Done) - which means requester should stop
685 /// asking for now.
686 ///
687 /// # Returns
688 ///
689 /// A [`PingUploadTask`] representing the next task.
690 pub fn get_upload_task(&self) -> PingUploadTask {
691 self.upload_manager.get_upload_task(self, self.log_pings())
692 }
693
694 /// Processes the response from an attempt to upload a ping.
695 ///
696 /// # Arguments
697 ///
698 /// * `uuid` - The UUID of the ping in question.
699 /// * `status` - The upload result.
700 pub fn process_ping_upload_response(
701 &self,
702 uuid: &str,
703 status: UploadResult,
704 ) -> UploadTaskAction {
705 self.upload_manager
706 .process_ping_upload_response(self, uuid, status)
707 }
708
709 /// Takes a snapshot for the given store and optionally clear it.
710 ///
711 /// # Arguments
712 ///
713 /// * `store_name` - The store to snapshot.
714 /// * `clear_store` - Whether to clear the store after snapshotting.
715 ///
716 /// # Returns
717 ///
718 /// The snapshot in a string encoded as JSON. If the snapshot is empty, returns an empty string.
719 pub fn snapshot(&mut self, store_name: &str, clear_store: bool) -> String {
720 StorageManager
721 .snapshot(self.storage(), store_name, clear_store)
722 .unwrap_or_else(|| String::from(""))
723 }
724
725 pub(crate) fn make_path(&self, ping_name: &str, doc_id: &str) -> String {
726 format!(
727 "/submit/{}/{}/{}/{}",
728 self.get_application_id(),
729 ping_name,
730 GLEAN_SCHEMA_VERSION,
731 doc_id
732 )
733 }
734
735 /// Collects and submits a ping by name for eventual uploading.
736 ///
737 /// The ping content is assembled as soon as possible, but upload is not
738 /// guaranteed to happen immediately, as that depends on the upload policies.
739 ///
740 /// If the ping currently contains no content, it will not be sent,
741 /// unless it is configured to be sent if empty.
742 ///
743 /// # Arguments
744 ///
745 /// * `ping_name` - The name of the ping to submit
746 /// * `reason` - A reason code to include in the ping
747 ///
748 /// # Returns
749 ///
750 /// Whether the ping was succesfully assembled and queued.
751 ///
752 /// # Errors
753 ///
754 /// If collecting or writing the ping to disk failed.
755 pub fn submit_ping_by_name(&self, ping_name: &str, reason: Option<&str>) -> bool {
756 match self.get_ping_by_name(ping_name) {
757 None => {
758 log::error!("Attempted to submit unknown ping '{}'", ping_name);
759 false
760 }
761 Some(ping) => ping.submit_sync(self, reason),
762 }
763 }
764
765 /// Gets a [`PingType`] by name.
766 ///
767 /// # Returns
768 ///
769 /// The [`PingType`] of a ping if the given name was registered before, [`None`]
770 /// otherwise.
771 pub fn get_ping_by_name(&self, ping_name: &str) -> Option<&PingType> {
772 self.ping_registry.get(ping_name)
773 }
774
775 /// Register a new [`PingType`](metrics/struct.PingType.html).
776 pub fn register_ping_type(&mut self, ping: &PingType) {
777 if self.ping_registry.contains_key(ping.name()) {
778 log::debug!("Duplicate ping named '{}'", ping.name())
779 }
780
781 self.ping_registry
782 .insert(ping.name().to_string(), ping.clone());
783 }
784
785 /// Gets a list of currently registered ping names.
786 ///
787 /// # Returns
788 ///
789 /// The list of ping names that are currently registered.
790 pub fn get_registered_ping_names(&self) -> Vec<&str> {
791 self.ping_registry.keys().map(String::as_str).collect()
792 }
793
794 /// Get create time of the Glean object.
795 pub(crate) fn start_time(&self) -> DateTime<FixedOffset> {
796 self.start_time
797 }
798
799 /// Indicates that an experiment is running.
800 ///
801 /// Glean will then add an experiment annotation to the environment
802 /// which is sent with pings. This information is not persisted between runs.
803 ///
804 /// # Arguments
805 ///
806 /// * `experiment_id` - The id of the active experiment (maximum 30 bytes).
807 /// * `branch` - The experiment branch (maximum 30 bytes).
808 /// * `extra` - Optional metadata to output with the ping.
809 pub fn set_experiment_active(
810 &self,
811 experiment_id: String,
812 branch: String,
813 extra: HashMap<String, String>,
814 ) {
815 let metric = ExperimentMetric::new(self, experiment_id);
816 metric.set_active_sync(self, branch, extra);
817 }
818
819 /// Indicates that an experiment is no longer running.
820 ///
821 /// # Arguments
822 ///
823 /// * `experiment_id` - The id of the active experiment to deactivate (maximum 30 bytes).
824 pub fn set_experiment_inactive(&self, experiment_id: String) {
825 let metric = ExperimentMetric::new(self, experiment_id);
826 metric.set_inactive_sync(self);
827 }
828
829 /// **Test-only API (exported for FFI purposes).**
830 ///
831 /// Gets stored data for the requested experiment.
832 ///
833 /// # Arguments
834 ///
835 /// * `experiment_id` - The id of the active experiment (maximum 30 bytes).
836 pub fn test_get_experiment_data(&self, experiment_id: String) -> Option<RecordedExperiment> {
837 let metric = ExperimentMetric::new(self, experiment_id);
838 metric.test_get_value(self)
839 }
840
841 /// **Test-only API (exported for FFI purposes).**
842 ///
843 /// Gets stored experimentation id annotation.
844 pub fn test_get_experimentation_id(&self) -> Option<String> {
845 self.additional_metrics
846 .experimentation_id
847 .get_value(self, None)
848 }
849
850 /// Set configuration to override the default state, typically initiated from a
851 /// remote_settings experiment or rollout
852 ///
853 /// # Arguments
854 ///
855 /// * `cfg` - The stringified JSON representation of a `RemoteSettingsConfig` object
856 pub fn apply_server_knobs_config(&self, cfg: RemoteSettingsConfig) {
857 // Set the current RemoteSettingsConfig, keeping the lock until the epoch is
858 // updated to prevent against reading a "new" config but an "old" epoch
859 let mut remote_settings_config = self.remote_settings_config.lock().unwrap();
860
861 // Merge the exising metrics configuration with the supplied one
862 remote_settings_config
863 .metrics_enabled
864 .extend(cfg.metrics_enabled);
865
866 // Merge the exising ping configuration with the supplied one
867 remote_settings_config
868 .pings_enabled
869 .extend(cfg.pings_enabled);
870
871 remote_settings_config.event_threshold = cfg.event_threshold;
872
873 // Update remote_settings epoch
874 self.remote_settings_epoch.fetch_add(1, Ordering::SeqCst);
875 }
876
877 /// Persists [`Lifetime::Ping`] data that might be in memory in case
878 /// [`delay_ping_lifetime_io`](InternalConfiguration::delay_ping_lifetime_io) is set
879 /// or was set at a previous time.
880 ///
881 /// If there is no data to persist, this function does nothing.
882 pub fn persist_ping_lifetime_data(&self) -> Result<()> {
883 if let Some(data) = self.data_store.as_ref() {
884 return data.persist_ping_lifetime_data();
885 }
886
887 Ok(())
888 }
889
890 /// Sets internally-handled application lifetime metrics.
891 fn set_application_lifetime_core_metrics(&self) {
892 self.core_metrics.os.set_sync(self, system::OS);
893 }
894
895 /// **This is not meant to be used directly.**
896 ///
897 /// Clears all the metrics that have [`Lifetime::Application`].
898 pub fn clear_application_lifetime_metrics(&self) {
899 log::trace!("Clearing Lifetime::Application metrics");
900 if let Some(data) = self.data_store.as_ref() {
901 data.clear_lifetime(Lifetime::Application);
902 }
903
904 // Set internally handled app lifetime metrics again.
905 self.set_application_lifetime_core_metrics();
906 }
907
908 /// Whether or not this is the first run on this profile.
909 pub fn is_first_run(&self) -> bool {
910 self.is_first_run
911 }
912
913 /// Sets a debug view tag.
914 ///
915 /// This will return `false` in case `value` is not a valid tag.
916 ///
917 /// When the debug view tag is set, pings are sent with a `X-Debug-ID` header with the value of the tag
918 /// and are sent to the ["Ping Debug Viewer"](https://mozilla.github.io/glean/book/dev/core/internal/debug-pings.html).
919 ///
920 /// # Arguments
921 ///
922 /// * `value` - A valid HTTP header value. Must match the regex: "[a-zA-Z0-9-]{1,20}".
923 pub fn set_debug_view_tag(&mut self, value: &str) -> bool {
924 self.debug.debug_view_tag.set(value.into())
925 }
926
927 /// Return the value for the debug view tag or [`None`] if it hasn't been set.
928 ///
929 /// The `debug_view_tag` may be set from an environment variable
930 /// (`GLEAN_DEBUG_VIEW_TAG`) or through the `set_debug_view_tag` function.
931 pub fn debug_view_tag(&self) -> Option<&String> {
932 self.debug.debug_view_tag.get()
933 }
934
935 /// Sets source tags.
936 ///
937 /// This will return `false` in case `value` contains invalid tags.
938 ///
939 /// Ping tags will show in the destination datasets, after ingestion.
940 ///
941 /// **Note** If one or more tags are invalid, all tags are ignored.
942 ///
943 /// # Arguments
944 ///
945 /// * `value` - A vector of at most 5 valid HTTP header values. Individual tags must match the regex: "[a-zA-Z0-9-]{1,20}".
946 pub fn set_source_tags(&mut self, value: Vec<String>) -> bool {
947 self.debug.source_tags.set(value)
948 }
949
950 /// Return the value for the source tags or [`None`] if it hasn't been set.
951 ///
952 /// The `source_tags` may be set from an environment variable (`GLEAN_SOURCE_TAGS`)
953 /// or through the [`set_source_tags`] function.
954 pub(crate) fn source_tags(&self) -> Option<&Vec<String>> {
955 self.debug.source_tags.get()
956 }
957
958 /// Sets the log pings debug option.
959 ///
960 /// This will return `false` in case we are unable to set the option.
961 ///
962 /// When the log pings debug option is `true`,
963 /// we log the payload of all succesfully assembled pings.
964 ///
965 /// # Arguments
966 ///
967 /// * `value` - The value of the log pings option
968 pub fn set_log_pings(&mut self, value: bool) -> bool {
969 self.debug.log_pings.set(value)
970 }
971
972 /// Return the value for the log pings debug option or `false` if it hasn't been set.
973 ///
974 /// The `log_pings` option may be set from an environment variable (`GLEAN_LOG_PINGS`)
975 /// or through the `set_log_pings` function.
976 pub fn log_pings(&self) -> bool {
977 self.debug.log_pings.get().copied().unwrap_or(false)
978 }
979
980 fn get_dirty_bit_metric(&self) -> metrics::BooleanMetric {
981 metrics::BooleanMetric::new(CommonMetricData {
982 name: "dirtybit".into(),
983 // We don't need a category, the name is already unique
984 category: "".into(),
985 send_in_pings: vec![INTERNAL_STORAGE.into()],
986 lifetime: Lifetime::User,
987 ..Default::default()
988 })
989 }
990
991 /// **This is not meant to be used directly.**
992 ///
993 /// Sets the value of a "dirty flag" in the permanent storage.
994 ///
995 /// The "dirty flag" is meant to have the following behaviour, implemented
996 /// by the consumers of the FFI layer:
997 ///
998 /// - on mobile: set to `false` when going to background or shutting down,
999 /// set to `true` at startup and when going to foreground.
1000 /// - on non-mobile platforms: set to `true` at startup and `false` at
1001 /// shutdown.
1002 ///
1003 /// At startup, before setting its new value, if the "dirty flag" value is
1004 /// `true`, then Glean knows it did not exit cleanly and can implement
1005 /// coping mechanisms (e.g. sending a `baseline` ping).
1006 pub fn set_dirty_flag(&self, new_value: bool) {
1007 self.get_dirty_bit_metric().set_sync(self, new_value);
1008 }
1009
1010 /// **This is not meant to be used directly.**
1011 ///
1012 /// Checks the stored value of the "dirty flag".
1013 pub fn is_dirty_flag_set(&self) -> bool {
1014 let dirty_bit_metric = self.get_dirty_bit_metric();
1015 match StorageManager.snapshot_metric(
1016 self.storage(),
1017 INTERNAL_STORAGE,
1018 &dirty_bit_metric.meta().identifier(self),
1019 dirty_bit_metric.meta().inner.lifetime,
1020 ) {
1021 Some(Metric::Boolean(b)) => b,
1022 _ => false,
1023 }
1024 }
1025
1026 /// Performs the collection/cleanup operations required by becoming active.
1027 ///
1028 /// This functions generates a baseline ping with reason `active`
1029 /// and then sets the dirty bit.
1030 pub fn handle_client_active(&mut self) {
1031 if !self
1032 .internal_pings
1033 .baseline
1034 .submit_sync(self, Some("active"))
1035 {
1036 log::info!("baseline ping not submitted on active");
1037 }
1038
1039 self.set_dirty_flag(true);
1040 }
1041
1042 /// Performs the collection/cleanup operations required by becoming inactive.
1043 ///
1044 /// This functions generates a baseline and an events ping with reason
1045 /// `inactive` and then clears the dirty bit.
1046 pub fn handle_client_inactive(&mut self) {
1047 if !self
1048 .internal_pings
1049 .baseline
1050 .submit_sync(self, Some("inactive"))
1051 {
1052 log::info!("baseline ping not submitted on inactive");
1053 }
1054
1055 if !self
1056 .internal_pings
1057 .events
1058 .submit_sync(self, Some("inactive"))
1059 {
1060 log::info!("events ping not submitted on inactive");
1061 }
1062
1063 self.set_dirty_flag(false);
1064 }
1065
1066 /// **Test-only API (exported for FFI purposes).**
1067 ///
1068 /// Deletes all stored metrics.
1069 ///
1070 /// Note that this also includes the ping sequence numbers, so it has
1071 /// the effect of resetting those to their initial values.
1072 pub fn test_clear_all_stores(&self) {
1073 if let Some(data) = self.data_store.as_ref() {
1074 data.clear_all()
1075 }
1076 // We don't care about this failing, maybe the data does just not exist.
1077 let _ = self.event_data_store.clear_all();
1078 }
1079
1080 /// Instructs the Metrics Ping Scheduler's thread to exit cleanly.
1081 /// If Glean was configured with `use_core_mps: false`, this has no effect.
1082 pub fn cancel_metrics_ping_scheduler(&self) {
1083 if self.schedule_metrics_pings {
1084 scheduler::cancel();
1085 }
1086 }
1087
1088 /// Instructs the Metrics Ping Scheduler to being scheduling metrics pings.
1089 /// If Glean wsa configured with `use_core_mps: false`, this has no effect.
1090 pub fn start_metrics_ping_scheduler(&self) {
1091 if self.schedule_metrics_pings {
1092 scheduler::schedule(self);
1093 }
1094 }
1095
1096 /// Updates attribution fields with new values.
1097 /// AttributionMetrics fields with `None` values will not overwrite older values.
1098 pub fn update_attribution(&self, attribution: AttributionMetrics) {
1099 if let Some(source) = attribution.source {
1100 self.core_metrics.attribution_source.set_sync(self, source);
1101 }
1102 if let Some(medium) = attribution.medium {
1103 self.core_metrics.attribution_medium.set_sync(self, medium);
1104 }
1105 if let Some(campaign) = attribution.campaign {
1106 self.core_metrics
1107 .attribution_campaign
1108 .set_sync(self, campaign);
1109 }
1110 if let Some(term) = attribution.term {
1111 self.core_metrics.attribution_term.set_sync(self, term);
1112 }
1113 if let Some(content) = attribution.content {
1114 self.core_metrics
1115 .attribution_content
1116 .set_sync(self, content);
1117 }
1118 }
1119
1120 /// **TEST-ONLY Method**
1121 ///
1122 /// Returns the current attribution metrics.
1123 pub fn test_get_attribution(&self) -> AttributionMetrics {
1124 AttributionMetrics {
1125 source: self
1126 .core_metrics
1127 .attribution_source
1128 .get_value(self, Some("glean_client_info")),
1129 medium: self
1130 .core_metrics
1131 .attribution_medium
1132 .get_value(self, Some("glean_client_info")),
1133 campaign: self
1134 .core_metrics
1135 .attribution_campaign
1136 .get_value(self, Some("glean_client_info")),
1137 term: self
1138 .core_metrics
1139 .attribution_term
1140 .get_value(self, Some("glean_client_info")),
1141 content: self
1142 .core_metrics
1143 .attribution_content
1144 .get_value(self, Some("glean_client_info")),
1145 }
1146 }
1147
1148 /// Updates distribution fields with new values.
1149 /// DistributionMetrics fields with `None` values will not overwrite older values.
1150 pub fn update_distribution(&self, distribution: DistributionMetrics) {
1151 if let Some(name) = distribution.name {
1152 self.core_metrics.distribution_name.set_sync(self, name);
1153 }
1154 }
1155
1156 /// **TEST-ONLY Method**
1157 ///
1158 /// Returns the current distribution metrics.
1159 pub fn test_get_distribution(&self) -> DistributionMetrics {
1160 DistributionMetrics {
1161 name: self
1162 .core_metrics
1163 .distribution_name
1164 .get_value(self, Some("glean_client_info")),
1165 }
1166 }
1167}