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}