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