From de287fad49f90d502e71caa8788f2f5545ff30f7 Mon Sep 17 00:00:00 2001 From: Jan-Erik Rediger Date: Tue, 14 Jul 2026 15:31:27 +0200 Subject: [PATCH 01/22] Put some state about the migration INTO the database --- glean-core/src/database/sqlite/schema.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/glean-core/src/database/sqlite/schema.rs b/glean-core/src/database/sqlite/schema.rs index cec5072214..619d26e3bd 100644 --- a/glean-core/src/database/sqlite/schema.rs +++ b/glean-core/src/database/sqlite/schema.rs @@ -74,9 +74,11 @@ impl ConnectionOpener for Schema { // Clients upgrading to schema 2 don't have the table. // But they did run through the migration. tx.execute_batch( - "CREATE TABLE migration(id INTEGER PRIMARY KEY, state TEXT NOT NULL);", + "CREATE TABLE migration( + id INTEGER PRIMARY KEY, + state TEXT NOT NULL + );", )?; - let cid_exists: Option = tx .query_row( "SELECT 1 FROM telemetry WHERE id = 'client_id'", From 73383e8c4d771639baee7860fd04b7d16a49c379 Mon Sep 17 00:00:00 2001 From: Charlie Humphreys Date: Tue, 4 Aug 2026 16:05:36 -0500 Subject: [PATCH 02/22] Bug 2060553: Add submitted_pings database table and add get/store methods --- Cargo.lock | 49 +++++++ glean-core/Cargo.toml | 2 +- glean-core/src/database/sqlite.rs | 176 +++++++++++++++++++++-- glean-core/src/database/sqlite/schema.rs | 26 +++- glean-core/src/error.rs | 12 ++ glean-core/tests/sqlite.rs | 52 ++++++- 6 files changed, 301 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c4d08b51f3..327a8b3dc3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -425,6 +425,12 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" + [[package]] name = "difflib" version = "0.4.0" @@ -962,6 +968,12 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + [[package]] name = "num-traits" version = "0.2.19" @@ -1059,6 +1071,12 @@ dependencies = [ "plotters-backend", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "predicates" version = "3.1.4" @@ -1251,6 +1269,7 @@ dependencies = [ "hashlink", "libsqlite3-sys", "smallvec", + "time", ] [[package]] @@ -1481,6 +1500,36 @@ dependencies = [ "syn", ] +[[package]] +name = "time" +version = "0.3.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85c17d80feb7334b40c484e45ed1a5273dfd8bfda537c3be2e74a06a6686f327" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcef1a61bdb119096e153208ec5cbec23944ce8bca13be5c7f60c634f7403935" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinytemplate" version = "1.2.1" diff --git a/glean-core/Cargo.toml b/glean-core/Cargo.toml index e973da9ecd..3435c0b244 100644 --- a/glean-core/Cargo.toml +++ b/glean-core/Cargo.toml @@ -44,7 +44,7 @@ uniffi = { version = "0.32.0", default-features = false } env_logger = { version = "0.10.0", default-features = false, optional = true } malloc_size_of_derive = "0.1.3" malloc_size_of = { version = "0.2.2", package = "wr_malloc_size_of", default-features = false, features = ["once_cell"] } -rusqlite = { version = "0.37.0", features = ["bundled"] } +rusqlite = { version = "0.37.0", features = ["bundled", "time"] } rmp-serde = "1.3.1" [target.'cfg(target_os = "android")'.dependencies] diff --git a/glean-core/src/database/sqlite.rs b/glean-core/src/database/sqlite.rs index 1db1eeb2d1..e3033fad86 100644 --- a/glean-core/src/database/sqlite.rs +++ b/glean-core/src/database/sqlite.rs @@ -2,31 +2,31 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -use std::fmt::{self, Display}; -use std::fs; -use std::num::NonZeroU64; -use std::path::Path; -use std::str; -use std::time::Duration; - +use chrono::{DateTime, FixedOffset}; +use connection::Connection; use malloc_size_of::MallocSizeOf; -use rusqlite::params; -use rusqlite::types::FromSqlError; +use rusqlite::fallible_iterator::FallibleIterator; +use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSqlOutput, ValueRef}; use rusqlite::OptionalExtension; use rusqlite::Transaction; +use rusqlite::{params, ToSql}; use rusqlite::{Error as SqlError, ErrorCode}; - -use connection::Connection; use schema::Schema; pub use schema::SchemaError; +use std::fmt::{self, Display}; +use std::fs; +use std::num::NonZeroU64; +use std::path::Path; +use std::str; +use std::time::Duration; use crate::common_metric_data::CommonMetricDataInternal; use crate::database::migration::{self, MigrationState}; use crate::metrics::dual_labeled_counter::RECORD_SEPARATOR; use crate::metrics::Metric; -use crate::Glean; use crate::Lifetime; use crate::Result; +use crate::{Error, Glean}; use super::ConnExt; @@ -72,6 +72,58 @@ impl MallocSizeOf for Database { } } +#[allow(unused)] +pub struct SubmittedPing { + pub document_id: String, + pub ping: String, + pub submitted_date: SqliteDatetime, + pub uploaded_date: Option, + pub value: Option>, +} + +impl SubmittedPing { + pub fn value(&self) -> Option { + self.value + .as_ref() + .map(|v| rmp_serde::from_slice(v).expect("IMPOSSIBLE: Deserializing value failed")) + } +} + +#[derive(Debug, Clone)] +pub struct SqliteDatetime(pub DateTime); + +impl TryFrom for SqliteDatetime { + type Error = Error; + + fn try_from(value: String) -> Result { + DateTime::parse_from_rfc3339(&value) + .map(SqliteDatetime) + .map_err(|e| e.into()) + } +} + +impl FromSql for SqliteDatetime { + fn column_result(value: ValueRef<'_>) -> FromSqlResult { + String::column_result(value).and_then(|as_string| { + DateTime::parse_from_rfc3339(&as_string) + .map(SqliteDatetime) + .map_err(FromSqlError::other) + }) + } +} + +impl PartialEq for SqliteDatetime { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} + +impl ToSql for SqliteDatetime { + fn to_sql(&self) -> rusqlite::Result> { + Ok(self.0.to_utc().to_rfc3339().into()) + } +} + const DEFAULT_DATABASE_FILE_NAME: &str = "glean.sqlite"; /// Calculate the database size from all the files in the directory. @@ -448,6 +500,106 @@ impl Database { .unwrap_or(false) } + pub fn get_all_submitted_pings(&self) -> Vec { + let get_all_submitted_pings_sql = r#" + SELECT * + FROM submitted_pings + ORDER BY date_submitted DESC + "#; + self.conn + .read(|conn| { + let Ok(mut stmt) = conn.prepare_cached(get_all_submitted_pings_sql) else { + return Ok(Default::default()); + }; + let Ok(pings_iter) = stmt.query([]) else { + return Ok(Default::default()); + }; + + pings_iter + .map(|r| { + Ok(SubmittedPing { + document_id: r.get(0).unwrap(), + ping: r.get(1).unwrap(), + submitted_date: r.get(2).unwrap(), + uploaded_date: r.get(3).unwrap(), + value: r.get(4).unwrap(), + }) + }) + .collect() + }) + .unwrap_or_default() + } + + pub fn get_submitted_pings(&self, ping: &str) -> Vec { + let get_submitted_pings_sql = r#" + SELECT + document_id, + ping, + date_submitted, + date_uploaded, + value + FROM submitted_pings + WHERE + ping = ?1 + ORDER BY date_submitted DESC + "#; + self.conn + .read(|conn| { + let Ok(mut stmt) = conn.prepare_cached(get_submitted_pings_sql) else { + return Ok(Default::default()); + }; + let Ok(pings_iter) = stmt.query([ping]) else { + return Ok(Default::default()); + }; + + pings_iter + .map(|r| { + Ok(SubmittedPing { + document_id: r.get(0).unwrap(), + ping: r.get(1).unwrap(), + submitted_date: r.get(2).unwrap(), + uploaded_date: r.get(3).unwrap(), + value: r.get(4).unwrap(), + }) + }) + .collect() + }) + .unwrap_or_default() + } + + pub fn store_submitted_ping( + &self, + document_id: String, + ping: String, + date_submitted: String, + date_uploaded: Option, + value: serde_json::Value, + ) -> Result<()> { + self.conn.write(|tx| { + let insert_sql = r#" + INSERT INTO + submitted_pings (document_id, ping, date_submitted, date_uploaded, value) + VALUES + (?1, ?2, ?3, ?4, ?5) + ON CONFLICT(document_id) DO UPDATE SET + ping = excluded.ping, + date_submitted = excluded.date_submitted, + date_uploaded = excluded.date_uploaded, + value = excluded.value + "#; + let mut stmt = tx.prepare_cached(insert_sql)?; + let encoded = rmp_serde::to_vec(&value).expect("IMPOSSIBLE: Serializing metric failed"); + stmt.execute(params![ + document_id, + ping, + SqliteDatetime::try_from(date_submitted).unwrap(), + date_uploaded.map(|d| SqliteDatetime::try_from(d).unwrap()), + encoded + ])?; + Ok(()) + }) + } + /// Records a metric in the underlying storage system. pub fn record(&self, glean: &Glean, data: &CommonMetricDataInternal, value: &Metric) { let name = data.base_identifier(); diff --git a/glean-core/src/database/sqlite/schema.rs b/glean-core/src/database/sqlite/schema.rs index 619d26e3bd..39e1f64360 100644 --- a/glean-core/src/database/sqlite/schema.rs +++ b/glean-core/src/database/sqlite/schema.rs @@ -16,7 +16,7 @@ use super::connection::ConnectionOpener; pub struct Schema; impl ConnectionOpener for Schema { - const MAX_SCHEMA_VERSION: u32 = 2; + const MAX_SCHEMA_VERSION: u32 = 3; type Error = SchemaError; @@ -62,6 +62,14 @@ impl ConnectionOpener for Schema { UNIQUE(id, ping, labels) ); CREATE TABLE migration(id INTEGER PRIMARY KEY, state TEXT NOT NULL); + CREATE TABLE submitted_pings( + document_id TEXT PRIMARY KEY, + ping TEXT NOT NULL, + date_submitted DATETIME NOT NULL, + date_uploaded DATETIME, + value BLOB + ); + CREATE INDEX submitted_pings_ping on submitted_pings(ping); ", )?; Ok(()) @@ -92,6 +100,22 @@ impl ConnectionOpener for Schema { } Ok(()) } + 3 => { + log::info!("Upgrading user_version to 3"); + // Clients upgrading to schema 3 don't have the table or index + tx.execute_batch( + "CREATE TABLE submitted_pings( + document_id TEXT PRIMARY KEY, + ping TEXT NOT NULL, + date_submitted TIMESTAMP NOT NULL, + date_uploaded TIMESTAMP, + value BLOB + ); + CREATE INDEX submitted_pings_ping on submitted_pings(ping);", + )?; + tx.execute("INSERT INTO migration (id, state) VALUES (2, 'done') ON CONFLICT(id) DO UPDATE SET state = excluded.state", [])?; + Ok(()) + } to_version => Err(SchemaError::UnsupportedSchemaVersion(to_version)), } } diff --git a/glean-core/src/error.rs b/glean-core/src/error.rs index 622ab3893f..a0a6de88d3 100644 --- a/glean-core/src/error.rs +++ b/glean-core/src/error.rs @@ -73,6 +73,9 @@ pub enum ErrorKind { /// Schema error Schema(SchemaError), + + /// Chrono parse error + Chrono(chrono::ParseError), } /// A specialized [`Error`] type for this crate's operations. @@ -131,6 +134,7 @@ impl Display for Error { UuidError(e) => write!(f, "Failed to parse UUID: {}", e), SQLite(e) => write!(f, "SQLite error: {}", e), Schema(e) => write!(f, "Schema error: {}", e), + Chrono(e) => write!(f, "Chrono time parse error: {}", e), } } } @@ -213,6 +217,14 @@ impl From for Error { } } +impl From for Error { + fn from(error: chrono::ParseError) -> Self { + Error { + kind: ErrorKind::Chrono(error), + } + } +} + #[derive(Debug)] pub enum ClientIdFileError { /// The file could not be found. diff --git a/glean-core/tests/sqlite.rs b/glean-core/tests/sqlite.rs index e65a5751d3..4df911adb4 100644 --- a/glean-core/tests/sqlite.rs +++ b/glean-core/tests/sqlite.rs @@ -259,7 +259,7 @@ fn database_externally_locked() { } #[test] -fn schema_v2_is_applied() { +fn latest_schema_is_applied() { let (first_client_id, temp) = { let (glean, temp) = new_glean(None); let client_id = clientid_metric().get_value(&glean, None).unwrap(); @@ -286,10 +286,58 @@ fn schema_v2_is_applied() { let cur_user_version: u32 = conn .query_one("PRAGMA user_version", [], |row| row.get(0)) .unwrap(); - assert_eq!(cur_user_version, 2); + assert_eq!(cur_user_version, 3); let migration_state: String = conn .query_one("SELECT state FROM migration", [], |row| row.get(0)) .unwrap(); assert_eq!(migration_state, "done"); } + +#[test] +fn test_storing_and_fetching_submitted_pings() { + let (glean, _temp) = new_glean(None); + + glean + .storage() + .store_submitted_ping( + "id".into(), + "ping".into(), + "2026-08-04T12:30:60.50Z".into(), + None, + serde_json::json!({ "test": "a value" }), + ) + .unwrap(); + + glean + .storage() + .store_submitted_ping( + "id-one".into(), + "ping-two".into(), + "2026-08-05T12:30:60.50Z".into(), + Some("2026-08-05T12:30:60.51Z".into()), + serde_json::json!({ "test": "a value" }), + ) + .unwrap(); + + let all_pings = glean.storage().get_all_submitted_pings(); + assert_eq!(all_pings.len(), 2); + assert_eq!(all_pings.first().unwrap().document_id, "id-one".to_string()); + assert_eq!(all_pings.last().unwrap().document_id, "id".to_string()); + assert_eq!( + all_pings.first().unwrap().submitted_date.0, + chrono::DateTime::parse_from_rfc3339("2026-08-05T12:30:60.50Z").unwrap() + ); + assert_eq!( + all_pings.first().unwrap().uploaded_date.clone().unwrap().0, + chrono::DateTime::parse_from_rfc3339("2026-08-05T12:30:60.51Z").unwrap() + ); + assert_eq!( + all_pings.first().unwrap().value().unwrap(), + serde_json::json!({ "test": "a value" }) + ); + + let some_pings = glean.storage().get_submitted_pings("ping"); + assert_eq!(some_pings.len(), 1); + assert_eq!(some_pings.first().unwrap().document_id, "id".to_string()); +} From b3c042b7622a854a0f593736a04e0f8780222299 Mon Sep 17 00:00:00 2001 From: Charlie Humphreys Date: Tue, 4 Aug 2026 16:36:05 -0500 Subject: [PATCH 03/22] update glean-sym-test Cargo.lock --- samples/glean-sym-test/Cargo.lock | 49 +++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/samples/glean-sym-test/Cargo.lock b/samples/glean-sym-test/Cargo.lock index 7a2c532ed0..62d63db7b2 100644 --- a/samples/glean-sym-test/Cargo.lock +++ b/samples/glean-sym-test/Cargo.lock @@ -313,6 +313,12 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" + [[package]] name = "displaydoc" version = "0.2.5" @@ -864,6 +870,12 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + [[package]] name = "num-traits" version = "0.2.19" @@ -957,6 +969,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "prettyplease" version = "0.2.37" @@ -1073,6 +1091,7 @@ dependencies = [ "hashlink", "libsqlite3-sys", "smallvec", + "time", ] [[package]] @@ -1299,6 +1318,36 @@ dependencies = [ "syn", ] +[[package]] +name = "time" +version = "0.3.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85c17d80feb7334b40c484e45ed1a5273dfd8bfda537c3be2e74a06a6686f327" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcef1a61bdb119096e153208ec5cbec23944ce8bca13be5c7f60c634f7403935" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinystr" version = "0.8.3" From ebe93b55ae46cbbeddf6b08d5fbcbf07bbc364e6 Mon Sep 17 00:00:00 2001 From: Charlie Humphreys Date: Tue, 4 Aug 2026 16:39:32 -0500 Subject: [PATCH 04/22] update CHANGELOG.md --- CHANGELOG.md | 2 ++ glean-core/benchmark/Cargo.lock | 49 +++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e287c44668..778011b8fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Unreleased changes +* Add new `submitted_pings` table to the SQLite database. Also added methods to store submitted pings and retrieve them ([#3575](https://github.com/mozilla/glean/pull/3575)). + [Full changelog](https://github.com/mozilla/glean/compare/v70.0.0...main) # v70.0.0 (2026-08-20) diff --git a/glean-core/benchmark/Cargo.lock b/glean-core/benchmark/Cargo.lock index 77d16c0c1d..b02f854c6b 100644 --- a/glean-core/benchmark/Cargo.lock +++ b/glean-core/benchmark/Cargo.lock @@ -425,6 +425,12 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" + [[package]] name = "derive_more" version = "2.1.1" @@ -961,6 +967,12 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + [[package]] name = "num-traits" version = "0.2.19" @@ -1077,6 +1089,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "prettyplease" version = "0.2.37" @@ -1235,6 +1253,7 @@ dependencies = [ "hashlink", "libsqlite3-sys", "smallvec", + "time", ] [[package]] @@ -1485,6 +1504,36 @@ dependencies = [ "syn", ] +[[package]] +name = "time" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinystr" version = "0.8.2" From b4aaead7f175e60da805e475bb09a575d2f76fec Mon Sep 17 00:00:00 2001 From: Charlie Humphreys Date: Wed, 5 Aug 2026 10:26:31 -0500 Subject: [PATCH 05/22] fix where create and migrate diverged --- glean-core/src/database/sqlite/schema.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/glean-core/src/database/sqlite/schema.rs b/glean-core/src/database/sqlite/schema.rs index 39e1f64360..30ec6ff8fe 100644 --- a/glean-core/src/database/sqlite/schema.rs +++ b/glean-core/src/database/sqlite/schema.rs @@ -107,8 +107,8 @@ impl ConnectionOpener for Schema { "CREATE TABLE submitted_pings( document_id TEXT PRIMARY KEY, ping TEXT NOT NULL, - date_submitted TIMESTAMP NOT NULL, - date_uploaded TIMESTAMP, + date_submitted DATETIME NOT NULL, + date_uploaded DATETIME, value BLOB ); CREATE INDEX submitted_pings_ping on submitted_pings(ping);", From 5ab6f40359f74dc2fb9d5b71ecb2f6c73cf0f6cd Mon Sep 17 00:00:00 2001 From: Charlie Humphreys Date: Thu, 6 Aug 2026 14:15:16 -0500 Subject: [PATCH 06/22] adjust test to account for re-insert --- glean-core/tests/sqlite.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/glean-core/tests/sqlite.rs b/glean-core/tests/sqlite.rs index 4df911adb4..64a853c089 100644 --- a/glean-core/tests/sqlite.rs +++ b/glean-core/tests/sqlite.rs @@ -298,6 +298,7 @@ fn latest_schema_is_applied() { fn test_storing_and_fetching_submitted_pings() { let (glean, _temp) = new_glean(None); + // First ping, no upload date glean .storage() .store_submitted_ping( @@ -309,6 +310,19 @@ fn test_storing_and_fetching_submitted_pings() { ) .unwrap(); + // Second ping, no upload date + glean + .storage() + .store_submitted_ping( + "id-one".into(), + "ping-two".into(), + "2026-08-05T12:30:60.50Z".into(), + None, + serde_json::json!({ "test": "a value" }), + ) + .unwrap(); + + // Second ping again, with upload date .01s after submitted date glean .storage() .store_submitted_ping( From d730fe92f8db5a0fe9a312d60a723271bc8abe75 Mon Sep 17 00:00:00 2001 From: Charlie Humphreys Date: Thu, 6 Aug 2026 14:34:21 -0500 Subject: [PATCH 07/22] update based on feedback --- glean-core/src/database/sqlite.rs | 42 +++++++++++-------- glean-core/src/database/sqlite/schema.rs | 52 ++++++++++++------------ 2 files changed, 51 insertions(+), 43 deletions(-) diff --git a/glean-core/src/database/sqlite.rs b/glean-core/src/database/sqlite.rs index e3033fad86..5874b89275 100644 --- a/glean-core/src/database/sqlite.rs +++ b/glean-core/src/database/sqlite.rs @@ -2,6 +2,13 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. +use std::fmt::{self, Display}; +use std::fs; +use std::num::NonZeroU64; +use std::path::Path; +use std::str; +use std::time::Duration; + use chrono::{DateTime, FixedOffset}; use connection::Connection; use malloc_size_of::MallocSizeOf; @@ -13,12 +20,6 @@ use rusqlite::{params, ToSql}; use rusqlite::{Error as SqlError, ErrorCode}; use schema::Schema; pub use schema::SchemaError; -use std::fmt::{self, Display}; -use std::fs; -use std::num::NonZeroU64; -use std::path::Path; -use std::str; -use std::time::Duration; use crate::common_metric_data::CommonMetricDataInternal; use crate::database::migration::{self, MigrationState}; @@ -502,7 +503,12 @@ impl Database { pub fn get_all_submitted_pings(&self) -> Vec { let get_all_submitted_pings_sql = r#" - SELECT * + SELECT + document_id, + ping, + date_submitted, + date_uploaded, + payload FROM submitted_pings ORDER BY date_submitted DESC "#; @@ -537,7 +543,7 @@ impl Database { ping, date_submitted, date_uploaded, - value + payload FROM submitted_pings WHERE ping = ?1 @@ -577,16 +583,16 @@ impl Database { ) -> Result<()> { self.conn.write(|tx| { let insert_sql = r#" - INSERT INTO - submitted_pings (document_id, ping, date_submitted, date_uploaded, value) - VALUES - (?1, ?2, ?3, ?4, ?5) - ON CONFLICT(document_id) DO UPDATE SET - ping = excluded.ping, - date_submitted = excluded.date_submitted, - date_uploaded = excluded.date_uploaded, - value = excluded.value - "#; + INSERT INTO + submitted_pings (document_id, ping, date_submitted, date_uploaded, payload) + VALUES + (?1, ?2, ?3, ?4, ?5) + ON CONFLICT(document_id) DO UPDATE SET + ping = excluded.ping, + date_submitted = excluded.date_submitted, + date_uploaded = excluded.date_uploaded, + value = excluded.value + "#; let mut stmt = tx.prepare_cached(insert_sql)?; let encoded = rmp_serde::to_vec(&value).expect("IMPOSSIBLE: Serializing metric failed"); stmt.execute(params![ diff --git a/glean-core/src/database/sqlite/schema.rs b/glean-core/src/database/sqlite/schema.rs index 30ec6ff8fe..06f866a637 100644 --- a/glean-core/src/database/sqlite/schema.rs +++ b/glean-core/src/database/sqlite/schema.rs @@ -53,23 +53,23 @@ impl ConnectionOpener for Schema { fn create(tx: &mut Transaction<'_>) -> Result<(), Self::Error> { tx.execute_batch( " - CREATE TABLE telemetry( - id TEXT NOT NULL, - ping TEXT NOT NULL, - lifetime TEXT NOT NULL, - labels TEXT NOT NULL, -- can't be null or ON CONFLICT won't work - value BLOB, - UNIQUE(id, ping, labels) - ); - CREATE TABLE migration(id INTEGER PRIMARY KEY, state TEXT NOT NULL); - CREATE TABLE submitted_pings( - document_id TEXT PRIMARY KEY, - ping TEXT NOT NULL, - date_submitted DATETIME NOT NULL, - date_uploaded DATETIME, - value BLOB - ); - CREATE INDEX submitted_pings_ping on submitted_pings(ping); + CREATE TABLE telemetry( + id TEXT NOT NULL, + ping TEXT NOT NULL, + lifetime TEXT NOT NULL, + labels TEXT NOT NULL, -- can't be null or ON CONFLICT won't work + value BLOB, + UNIQUE(id, ping, labels) + ); + CREATE TABLE migration(id INTEGER PRIMARY KEY, state TEXT NOT NULL); + CREATE TABLE submitted_pings( + document_id TEXT PRIMARY KEY, + ping TEXT NOT NULL, + date_submitted DATETIME NOT NULL, + date_uploaded DATETIME, + payload BLOB + ); + CREATE INDEX submitted_pings_ping on submitted_pings(ping); ", )?; Ok(()) @@ -104,14 +104,16 @@ impl ConnectionOpener for Schema { log::info!("Upgrading user_version to 3"); // Clients upgrading to schema 3 don't have the table or index tx.execute_batch( - "CREATE TABLE submitted_pings( - document_id TEXT PRIMARY KEY, - ping TEXT NOT NULL, - date_submitted DATETIME NOT NULL, - date_uploaded DATETIME, - value BLOB - ); - CREATE INDEX submitted_pings_ping on submitted_pings(ping);", + " + CREATE TABLE submitted_pings( + document_id TEXT PRIMARY KEY, + ping TEXT NOT NULL, + date_submitted DATETIME NOT NULL, + date_uploaded DATETIME, + payload BLOB + ); + CREATE INDEX submitted_pings_ping on submitted_pings(ping); + ", )?; tx.execute("INSERT INTO migration (id, state) VALUES (2, 'done') ON CONFLICT(id) DO UPDATE SET state = excluded.state", [])?; Ok(()) From 4ca57bec3278cf2147b6b1364894ed3480245326 Mon Sep 17 00:00:00 2001 From: Charlie Humphreys Date: Thu, 6 Aug 2026 14:42:15 -0500 Subject: [PATCH 08/22] fix issue with insert query --- glean-core/src/database/sqlite.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glean-core/src/database/sqlite.rs b/glean-core/src/database/sqlite.rs index 5874b89275..2466381f65 100644 --- a/glean-core/src/database/sqlite.rs +++ b/glean-core/src/database/sqlite.rs @@ -591,7 +591,7 @@ impl Database { ping = excluded.ping, date_submitted = excluded.date_submitted, date_uploaded = excluded.date_uploaded, - value = excluded.value + payload = excluded.payload "#; let mut stmt = tx.prepare_cached(insert_sql)?; let encoded = rmp_serde::to_vec(&value).expect("IMPOSSIBLE: Serializing metric failed"); From d1d7d761205219aa1d48e6dc3bfa8cbf977e4007 Mon Sep 17 00:00:00 2001 From: Charlie Humphreys Date: Tue, 11 Aug 2026 08:59:48 -0500 Subject: [PATCH 09/22] update changelog and fix rebase issue --- CHANGELOG.md | 4 ++-- glean-core/src/database/sqlite/schema.rs | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 778011b8fc..048e90bd82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Unreleased changes -* Add new `submitted_pings` table to the SQLite database. Also added methods to store submitted pings and retrieve them ([#3575](https://github.com/mozilla/glean/pull/3575)). +* Add new `submitted_pings` table to the SQLite database. Also added methods to store submitted pings and retrieve them ([#3585](https://github.com/mozilla/glean/pull/3585)). [Full changelog](https://github.com/mozilla/glean/compare/v70.0.0...main) @@ -24,8 +24,8 @@ * Implement glean-noop as a feature of glean-sym ([#3541](https://github.com/mozilla/glean/pull/3541)) * Support pings ([#3544](https://github.com/mozilla/glean/pull/3544)) * Implement the event metric ([#3534](https://github.com/mozilla/glean/pull/3534)) - * BREAKING CHANGE: Switch from a noop feature to an `active` feature ([#3583](https://github.com/mozilla/glean/pull/3583)) * iOS + * BREAKING CHANGE: Switch from a noop feature to an `active` feature ([#3583](https://github.com/mozilla/glean/pull/3583)) * Implement the custom distribution metric type ([#3572](https://github.com/mozilla/glean/pull/3572)) * Python * Implement the custom distribution metric type ([#3572](https://github.com/mozilla/glean/pull/3572)) diff --git a/glean-core/src/database/sqlite/schema.rs b/glean-core/src/database/sqlite/schema.rs index 06f866a637..3bb4428a9e 100644 --- a/glean-core/src/database/sqlite/schema.rs +++ b/glean-core/src/database/sqlite/schema.rs @@ -82,11 +82,9 @@ impl ConnectionOpener for Schema { // Clients upgrading to schema 2 don't have the table. // But they did run through the migration. tx.execute_batch( - "CREATE TABLE migration( - id INTEGER PRIMARY KEY, - state TEXT NOT NULL - );", + "CREATE TABLE migration(id INTEGER PRIMARY KEY, state TEXT NOT NULL);", )?; + let cid_exists: Option = tx .query_row( "SELECT 1 FROM telemetry WHERE id = 'client_id'", From df19ff69cb82f4a2f952fb29f42975b77d19d53a Mon Sep 17 00:00:00 2001 From: Charlie Humphreys Date: Wed, 12 Aug 2026 15:56:21 -0500 Subject: [PATCH 10/22] update Glean to have configuration options for enabling storing pings --- Cargo.lock | 49 -------------------- glean-core/Cargo.toml | 2 +- glean-core/benchmark/Cargo.lock | 49 -------------------- glean-core/src/core/mod.rs | 8 ++++ glean-core/src/database/sqlite.rs | 59 +++++++++++++----------- glean-core/src/database/sqlite/schema.rs | 8 ++-- glean-core/src/glean.udl | 3 ++ glean-core/src/lib.rs | 13 ++++++ glean-core/src/metrics/ping.rs | 8 +++- glean-core/tests/common/mod.rs | 1 + glean-core/tests/session.rs | 1 + glean-core/tests/sqlite.rs | 23 ++++++--- 12 files changed, 87 insertions(+), 137 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 327a8b3dc3..c4d08b51f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -425,12 +425,6 @@ dependencies = [ "num_cpus", ] -[[package]] -name = "deranged" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" - [[package]] name = "difflib" version = "0.4.0" @@ -968,12 +962,6 @@ dependencies = [ "minimal-lexical", ] -[[package]] -name = "num-conv" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" - [[package]] name = "num-traits" version = "0.2.19" @@ -1071,12 +1059,6 @@ dependencies = [ "plotters-backend", ] -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - [[package]] name = "predicates" version = "3.1.4" @@ -1269,7 +1251,6 @@ dependencies = [ "hashlink", "libsqlite3-sys", "smallvec", - "time", ] [[package]] @@ -1500,36 +1481,6 @@ dependencies = [ "syn", ] -[[package]] -name = "time" -version = "0.3.51" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85c17d80feb7334b40c484e45ed1a5273dfd8bfda537c3be2e74a06a6686f327" -dependencies = [ - "deranged", - "num-conv", - "powerfmt", - "serde_core", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" - -[[package]] -name = "time-macros" -version = "0.2.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcef1a61bdb119096e153208ec5cbec23944ce8bca13be5c7f60c634f7403935" -dependencies = [ - "num-conv", - "time-core", -] - [[package]] name = "tinytemplate" version = "1.2.1" diff --git a/glean-core/Cargo.toml b/glean-core/Cargo.toml index 3435c0b244..e973da9ecd 100644 --- a/glean-core/Cargo.toml +++ b/glean-core/Cargo.toml @@ -44,7 +44,7 @@ uniffi = { version = "0.32.0", default-features = false } env_logger = { version = "0.10.0", default-features = false, optional = true } malloc_size_of_derive = "0.1.3" malloc_size_of = { version = "0.2.2", package = "wr_malloc_size_of", default-features = false, features = ["once_cell"] } -rusqlite = { version = "0.37.0", features = ["bundled", "time"] } +rusqlite = { version = "0.37.0", features = ["bundled"] } rmp-serde = "1.3.1" [target.'cfg(target_os = "android")'.dependencies] diff --git a/glean-core/benchmark/Cargo.lock b/glean-core/benchmark/Cargo.lock index b02f854c6b..77d16c0c1d 100644 --- a/glean-core/benchmark/Cargo.lock +++ b/glean-core/benchmark/Cargo.lock @@ -425,12 +425,6 @@ dependencies = [ "num_cpus", ] -[[package]] -name = "deranged" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" - [[package]] name = "derive_more" version = "2.1.1" @@ -967,12 +961,6 @@ dependencies = [ "minimal-lexical", ] -[[package]] -name = "num-conv" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" - [[package]] name = "num-traits" version = "0.2.19" @@ -1089,12 +1077,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - [[package]] name = "prettyplease" version = "0.2.37" @@ -1253,7 +1235,6 @@ dependencies = [ "hashlink", "libsqlite3-sys", "smallvec", - "time", ] [[package]] @@ -1504,36 +1485,6 @@ dependencies = [ "syn", ] -[[package]] -name = "time" -version = "0.3.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" -dependencies = [ - "deranged", - "num-conv", - "powerfmt", - "serde_core", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" - -[[package]] -name = "time-macros" -version = "0.2.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" -dependencies = [ - "num-conv", - "time-core", -] - [[package]] name = "tinystr" version = "0.8.2" diff --git a/glean-core/src/core/mod.rs b/glean-core/src/core/mod.rs index 397625dd00..24f4a97dd0 100644 --- a/glean-core/src/core/mod.rs +++ b/glean-core/src/core/mod.rs @@ -146,6 +146,7 @@ where /// session_sample_rate: 1.0, /// session_inactivity_timeout_ms: 1_800_000, /// events_ping_acceleration_factor: None, +/// enable_store_submitted_pings: false, /// }; /// let mut glean = Glean::new(cfg).unwrap(); /// let ping = PingType::new("sample", true, false, true, true, true, vec![], vec![], true, vec![]); @@ -196,6 +197,7 @@ pub struct Glean { #[ignore_malloc_size_of = "TODO: Expose session memory allocations (bug 2043355)"] pub(crate) session_manager: SessionManager, events_ping_acceleration_factor: Option, + pub(crate) store_submitted_pings_enabled: bool, } impl Glean { @@ -279,6 +281,7 @@ impl Glean { events_ping_acceleration_factor: cfg .events_ping_acceleration_factor .map(|x| x as usize), + store_submitted_pings_enabled: cfg.enable_store_submitted_pings }; // Ensuring these pings are registered. @@ -569,6 +572,10 @@ impl Glean { // to ensure we don't enqueue pings before their files are deleted. let _scanning_thread = glean.upload_manager.scan_pending_pings_directories(true); + if cfg.enable_store_submitted_pings { + + } + Ok(glean) } @@ -604,6 +611,7 @@ impl Glean { session_sample_rate: 1.0, session_inactivity_timeout_ms: 1_800_000, events_ping_acceleration_factor: None, + enable_store_submitted_pings: false, }; let mut glean = Self::new(cfg).unwrap(); diff --git a/glean-core/src/database/sqlite.rs b/glean-core/src/database/sqlite.rs index 2466381f65..302f1d7034 100644 --- a/glean-core/src/database/sqlite.rs +++ b/glean-core/src/database/sqlite.rs @@ -9,7 +9,7 @@ use std::path::Path; use std::str; use std::time::Duration; -use chrono::{DateTime, FixedOffset}; +use chrono::{DateTime, Utc}; use connection::Connection; use malloc_size_of::MallocSizeOf; use rusqlite::fallible_iterator::FallibleIterator; @@ -27,7 +27,7 @@ use crate::metrics::dual_labeled_counter::RECORD_SEPARATOR; use crate::metrics::Metric; use crate::Lifetime; use crate::Result; -use crate::{Error, Glean}; +use crate::Glean; use super::ConnExt; @@ -91,24 +91,21 @@ impl SubmittedPing { } #[derive(Debug, Clone)] -pub struct SqliteDatetime(pub DateTime); +pub struct SqliteDatetime(pub DateTime); -impl TryFrom for SqliteDatetime { - type Error = Error; - - fn try_from(value: String) -> Result { - DateTime::parse_from_rfc3339(&value) - .map(SqliteDatetime) - .map_err(|e| e.into()) +impl ToSql for SqliteDatetime { + fn to_sql(&self) -> rusqlite::Result> { + Ok(ToSqlOutput::from(self.0.timestamp_millis())) } } impl FromSql for SqliteDatetime { fn column_result(value: ValueRef<'_>) -> FromSqlResult { - String::column_result(value).and_then(|as_string| { - DateTime::parse_from_rfc3339(&as_string) - .map(SqliteDatetime) - .map_err(FromSqlError::other) + i64::column_result(value).and_then(|as_i64| { + match DateTime::from_timestamp_millis(as_i64) { + Some(d) => Ok(SqliteDatetime(d)), + None => Err(FromSqlError::InvalidType) + } }) } } @@ -119,12 +116,6 @@ impl PartialEq for SqliteDatetime { } } -impl ToSql for SqliteDatetime { - fn to_sql(&self) -> rusqlite::Result> { - Ok(self.0.to_utc().to_rfc3339().into()) - } -} - const DEFAULT_DATABASE_FILE_NAME: &str = "glean.sqlite"; /// Calculate the database size from all the files in the directory. @@ -573,12 +564,28 @@ impl Database { .unwrap_or_default() } + pub fn mark_ping_as_uploaded(&self, document_id: &str, date_uploaded: DateTime) -> usize { + let get_submitted_pings_sql = r#" + UPDATE submitted_pings + SET date_uploaded = ?1 + WHERE document_id = ?2 + "#; + self.conn + .write(|conn| { + let Ok(mut stmt) = conn.prepare_cached(get_submitted_pings_sql) else { + return Ok(Default::default()); + }; + stmt.execute(params![SqliteDatetime(date_uploaded), document_id]) + }) + .unwrap_or_default() + } + pub fn store_submitted_ping( &self, - document_id: String, - ping: String, - date_submitted: String, - date_uploaded: Option, + document_id: &str, + ping: &str, + date_submitted: DateTime, + date_uploaded: Option>, value: serde_json::Value, ) -> Result<()> { self.conn.write(|tx| { @@ -598,8 +605,8 @@ impl Database { stmt.execute(params![ document_id, ping, - SqliteDatetime::try_from(date_submitted).unwrap(), - date_uploaded.map(|d| SqliteDatetime::try_from(d).unwrap()), + SqliteDatetime(date_submitted), + date_uploaded.map(SqliteDatetime), encoded ])?; Ok(()) diff --git a/glean-core/src/database/sqlite/schema.rs b/glean-core/src/database/sqlite/schema.rs index 3bb4428a9e..5e74989de9 100644 --- a/glean-core/src/database/sqlite/schema.rs +++ b/glean-core/src/database/sqlite/schema.rs @@ -65,8 +65,8 @@ impl ConnectionOpener for Schema { CREATE TABLE submitted_pings( document_id TEXT PRIMARY KEY, ping TEXT NOT NULL, - date_submitted DATETIME NOT NULL, - date_uploaded DATETIME, + date_submitted INTEGER NOT NULL, + date_uploaded INTEGER, payload BLOB ); CREATE INDEX submitted_pings_ping on submitted_pings(ping); @@ -106,8 +106,8 @@ impl ConnectionOpener for Schema { CREATE TABLE submitted_pings( document_id TEXT PRIMARY KEY, ping TEXT NOT NULL, - date_submitted DATETIME NOT NULL, - date_uploaded DATETIME, + date_submitted INTEGER NOT NULL, + date_uploaded INTEGER, payload BLOB ); CREATE INDEX submitted_pings_ping on submitted_pings(ping); diff --git a/glean-core/src/glean.udl b/glean-core/src/glean.udl index 128393d09e..6291b09977 100644 --- a/glean-core/src/glean.udl +++ b/glean-core/src/glean.udl @@ -27,6 +27,8 @@ namespace glean { void glean_set_upload_enabled(boolean enabled); + void glean_set_store_submitted_pings_enabled(boolean enabled); + // Experiment reporting API void glean_set_experiment_active(string experiment_id, string branch, record extra); void glean_set_experiment_inactive(string experiment_id); @@ -127,6 +129,7 @@ dictionary InternalConfiguration { f64 session_sample_rate; // Must be in [0.0, 1.0]; values outside are clamped. u64 session_inactivity_timeout_ms; // Milliseconds; 0 means sessions never time out. u32? events_ping_acceleration_factor; + boolean enable_store_submitted_pings; }; // Session management mode. diff --git a/glean-core/src/lib.rs b/glean-core/src/lib.rs index 23c156ce0d..4f1f87c430 100644 --- a/glean-core/src/lib.rs +++ b/glean-core/src/lib.rs @@ -184,6 +184,8 @@ pub struct InternalConfiguration { pub session_inactivity_timeout_ms: u64, /// The number of "events" pings to accelerate each session, plus one. pub events_ping_acceleration_factor: Option, + /// Whether to store submitted pings. Default: false + pub enable_store_submitted_pings: bool, } /// How to specify the rate at which pings may be uploaded before they are throttled. @@ -965,6 +967,17 @@ pub fn glean_set_collection_enabled(enabled: bool) { glean_set_upload_enabled(enabled) } +/// Sets whether Glean should store submitted pings or not. +pub fn glean_set_store_submitted_pings_enabled(enabled: bool) { + if !was_initialize_called() { + return; + } + + launch_with_glean_mut(move |glean| { + glean.store_submitted_pings_enabled = enabled; + }); +} + /// Enable or disable a ping. /// /// Disabling a ping causes all data for that ping to be removed from storage diff --git a/glean-core/src/metrics/ping.rs b/glean-core/src/metrics/ping.rs index fff04ddaa1..8f7e7c8b07 100644 --- a/glean-core/src/metrics/ping.rs +++ b/glean-core/src/metrics/ping.rs @@ -5,7 +5,7 @@ use std::fmt; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; - +use chrono::Utc; use crate::ping::PingMaker; use crate::upload::PingPayload; use crate::Glean; @@ -353,6 +353,12 @@ impl PingType { .add_sync(glean, 1); } + if glean.store_submitted_pings_enabled { + if let Err(e) = glean.storage().store_submitted_ping(ping.doc_id, &self.0.name, Utc::now(), None, ping.content.clone()) { + log::warn!("{}", e); + } + } + if let Err(e) = ping_maker.store_ping(glean.get_data_path(), &ping) { log::warn!( "IO error while writing ping to file: {}. Enqueuing upload of what we have in memory.", diff --git a/glean-core/tests/common/mod.rs b/glean-core/tests/common/mod.rs index b602beecb6..96e88f4fb2 100644 --- a/glean-core/tests/common/mod.rs +++ b/glean-core/tests/common/mod.rs @@ -75,6 +75,7 @@ pub fn new_glean_with_upload( session_sample_rate: 1.0, session_inactivity_timeout_ms: 1_800_000, events_ping_acceleration_factor: None, + enable_store_submitted_pings: false, }; let mut glean = Glean::new(cfg).unwrap(); diff --git a/glean-core/tests/session.rs b/glean-core/tests/session.rs index 4c51a6da49..c857da2234 100644 --- a/glean-core/tests/session.rs +++ b/glean-core/tests/session.rs @@ -47,6 +47,7 @@ fn session_cfg( session_sample_rate: sample_rate, session_inactivity_timeout_ms: timeout_ms, events_ping_acceleration_factor: None, + enable_store_submitted_pings: false, } } diff --git a/glean-core/tests/sqlite.rs b/glean-core/tests/sqlite.rs index 64a853c089..6554d2399b 100644 --- a/glean-core/tests/sqlite.rs +++ b/glean-core/tests/sqlite.rs @@ -4,7 +4,7 @@ mod common; use std::fs; - +use chrono::Utc; use crate::common::*; use glean_core::metrics::*; @@ -205,6 +205,7 @@ mod unix { session_sample_rate: 1.0, session_inactivity_timeout_ms: 1_800_000, events_ping_acceleration_factor: None, + enable_store_submitted_pings: true, }; let glean = Glean::new(cfg); assert!(glean.is_err()); @@ -253,6 +254,7 @@ fn database_externally_locked() { session_mode: SessionMode::Auto, session_sample_rate: 1.0, events_ping_acceleration_factor: None, + enable_store_submitted_pings: true, }; let glean = Glean::new(cfg); assert!(glean.is_err()); @@ -298,13 +300,16 @@ fn latest_schema_is_applied() { fn test_storing_and_fetching_submitted_pings() { let (glean, _temp) = new_glean(None); + let utc_time_one = chrono::DateTime::parse_from_rfc3339("2026-08-05T12:30:00.50Z").unwrap().to_utc(); + let utc_time_two = chrono::DateTime::parse_from_rfc3339("2026-08-05T12:30:00.51Z").unwrap().to_utc(); + // First ping, no upload date glean .storage() .store_submitted_ping( "id".into(), "ping".into(), - "2026-08-04T12:30:60.50Z".into(), + utc_time_one, None, serde_json::json!({ "test": "a value" }), ) @@ -316,7 +321,7 @@ fn test_storing_and_fetching_submitted_pings() { .store_submitted_ping( "id-one".into(), "ping-two".into(), - "2026-08-05T12:30:60.50Z".into(), + utc_time_two, None, serde_json::json!({ "test": "a value" }), ) @@ -328,8 +333,8 @@ fn test_storing_and_fetching_submitted_pings() { .store_submitted_ping( "id-one".into(), "ping-two".into(), - "2026-08-05T12:30:60.50Z".into(), - Some("2026-08-05T12:30:60.51Z".into()), + utc_time_two, + Some(utc_time_two), serde_json::json!({ "test": "a value" }), ) .unwrap(); @@ -340,18 +345,22 @@ fn test_storing_and_fetching_submitted_pings() { assert_eq!(all_pings.last().unwrap().document_id, "id".to_string()); assert_eq!( all_pings.first().unwrap().submitted_date.0, - chrono::DateTime::parse_from_rfc3339("2026-08-05T12:30:60.50Z").unwrap() + utc_time_two ); assert_eq!( all_pings.first().unwrap().uploaded_date.clone().unwrap().0, - chrono::DateTime::parse_from_rfc3339("2026-08-05T12:30:60.51Z").unwrap() + utc_time_two ); assert_eq!( all_pings.first().unwrap().value().unwrap(), serde_json::json!({ "test": "a value" }) ); + let count = glean.storage().mark_ping_as_uploaded("id", utc_time_one); + assert_eq!(count, 1); + let some_pings = glean.storage().get_submitted_pings("ping"); assert_eq!(some_pings.len(), 1); assert_eq!(some_pings.first().unwrap().document_id, "id".to_string()); + assert_eq!(some_pings.first().unwrap().uploaded_date.clone().unwrap().0, utc_time_one); } From 041669f5d47198d61aef29f73b22175c4bb5d72a Mon Sep 17 00:00:00 2001 From: Charlie Humphreys Date: Wed, 12 Aug 2026 15:57:20 -0500 Subject: [PATCH 11/22] fix formatting --- glean-core/src/core/mod.rs | 4 +--- glean-core/src/database/sqlite.rs | 10 ++++------ glean-core/src/metrics/ping.rs | 16 +++++++++++----- glean-core/tests/sqlite.rs | 22 +++++++++++++--------- 4 files changed, 29 insertions(+), 23 deletions(-) diff --git a/glean-core/src/core/mod.rs b/glean-core/src/core/mod.rs index 24f4a97dd0..57bf2eac35 100644 --- a/glean-core/src/core/mod.rs +++ b/glean-core/src/core/mod.rs @@ -572,9 +572,7 @@ impl Glean { // to ensure we don't enqueue pings before their files are deleted. let _scanning_thread = glean.upload_manager.scan_pending_pings_directories(true); - if cfg.enable_store_submitted_pings { - - } + if cfg.enable_store_submitted_pings {} Ok(glean) } diff --git a/glean-core/src/database/sqlite.rs b/glean-core/src/database/sqlite.rs index 302f1d7034..71454cf229 100644 --- a/glean-core/src/database/sqlite.rs +++ b/glean-core/src/database/sqlite.rs @@ -25,9 +25,9 @@ use crate::common_metric_data::CommonMetricDataInternal; use crate::database::migration::{self, MigrationState}; use crate::metrics::dual_labeled_counter::RECORD_SEPARATOR; use crate::metrics::Metric; +use crate::Glean; use crate::Lifetime; use crate::Result; -use crate::Glean; use super::ConnExt; @@ -101,11 +101,9 @@ impl ToSql for SqliteDatetime { impl FromSql for SqliteDatetime { fn column_result(value: ValueRef<'_>) -> FromSqlResult { - i64::column_result(value).and_then(|as_i64| { - match DateTime::from_timestamp_millis(as_i64) { - Some(d) => Ok(SqliteDatetime(d)), - None => Err(FromSqlError::InvalidType) - } + i64::column_result(value).and_then(|as_i64| match DateTime::from_timestamp_millis(as_i64) { + Some(d) => Ok(SqliteDatetime(d)), + None => Err(FromSqlError::InvalidType), }) } } diff --git a/glean-core/src/metrics/ping.rs b/glean-core/src/metrics/ping.rs index 8f7e7c8b07..12e917ba10 100644 --- a/glean-core/src/metrics/ping.rs +++ b/glean-core/src/metrics/ping.rs @@ -2,13 +2,13 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -use std::fmt; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::Arc; -use chrono::Utc; use crate::ping::PingMaker; use crate::upload::PingPayload; use crate::Glean; +use chrono::Utc; +use std::fmt; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; use malloc_size_of_derive::MallocSizeOf; use uuid::Uuid; @@ -354,7 +354,13 @@ impl PingType { } if glean.store_submitted_pings_enabled { - if let Err(e) = glean.storage().store_submitted_ping(ping.doc_id, &self.0.name, Utc::now(), None, ping.content.clone()) { + if let Err(e) = glean.storage().store_submitted_ping( + ping.doc_id, + &self.0.name, + Utc::now(), + None, + ping.content.clone(), + ) { log::warn!("{}", e); } } diff --git a/glean-core/tests/sqlite.rs b/glean-core/tests/sqlite.rs index 6554d2399b..0b4870daa0 100644 --- a/glean-core/tests/sqlite.rs +++ b/glean-core/tests/sqlite.rs @@ -3,9 +3,9 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. mod common; -use std::fs; -use chrono::Utc; use crate::common::*; +use chrono::Utc; +use std::fs; use glean_core::metrics::*; use glean_core::CommonMetricData; @@ -300,8 +300,12 @@ fn latest_schema_is_applied() { fn test_storing_and_fetching_submitted_pings() { let (glean, _temp) = new_glean(None); - let utc_time_one = chrono::DateTime::parse_from_rfc3339("2026-08-05T12:30:00.50Z").unwrap().to_utc(); - let utc_time_two = chrono::DateTime::parse_from_rfc3339("2026-08-05T12:30:00.51Z").unwrap().to_utc(); + let utc_time_one = chrono::DateTime::parse_from_rfc3339("2026-08-05T12:30:00.50Z") + .unwrap() + .to_utc(); + let utc_time_two = chrono::DateTime::parse_from_rfc3339("2026-08-05T12:30:00.51Z") + .unwrap() + .to_utc(); // First ping, no upload date glean @@ -343,10 +347,7 @@ fn test_storing_and_fetching_submitted_pings() { assert_eq!(all_pings.len(), 2); assert_eq!(all_pings.first().unwrap().document_id, "id-one".to_string()); assert_eq!(all_pings.last().unwrap().document_id, "id".to_string()); - assert_eq!( - all_pings.first().unwrap().submitted_date.0, - utc_time_two - ); + assert_eq!(all_pings.first().unwrap().submitted_date.0, utc_time_two); assert_eq!( all_pings.first().unwrap().uploaded_date.clone().unwrap().0, utc_time_two @@ -362,5 +363,8 @@ fn test_storing_and_fetching_submitted_pings() { let some_pings = glean.storage().get_submitted_pings("ping"); assert_eq!(some_pings.len(), 1); assert_eq!(some_pings.first().unwrap().document_id, "id".to_string()); - assert_eq!(some_pings.first().unwrap().uploaded_date.clone().unwrap().0, utc_time_one); + assert_eq!( + some_pings.first().unwrap().uploaded_date.clone().unwrap().0, + utc_time_one + ); } From 2ad08c5f121bbbc444d046898976429101e09f65 Mon Sep 17 00:00:00 2001 From: Charlie Humphreys Date: Thu, 13 Aug 2026 14:06:29 -0500 Subject: [PATCH 12/22] add cleanup method that runs on shutdown/maintenance --- glean-core/src/core/mod.rs | 12 +++++- glean-core/src/database/sqlite.rs | 68 +++++++++++++++++++++++++++-- glean-core/src/lib_unit_tests.rs | 1 + glean-core/src/upload/mod.rs | 54 +++++++++++++++++++++++ glean-core/tests/sqlite.rs | 71 ++++++++++++++++++++++++++++--- 5 files changed, 195 insertions(+), 11 deletions(-) diff --git a/glean-core/src/core/mod.rs b/glean-core/src/core/mod.rs index 57bf2eac35..9c5ece8a72 100644 --- a/glean-core/src/core/mod.rs +++ b/glean-core/src/core/mod.rs @@ -572,8 +572,6 @@ impl Glean { // to ensure we don't enqueue pings before their files are deleted. let _scanning_thread = glean.upload_manager.scan_pending_pings_directories(true); - if cfg.enable_store_submitted_pings {} - Ok(glean) } @@ -823,6 +821,16 @@ impl Glean { } } + /// Sets whether storing submitted pings is enabled or not. + /// + /// # Arguments + /// + /// * `enabled` - When true, enables storing submitted pings. + /// + pub fn set_store_submitted_pings_enabled(&mut self, enabled: bool) { + self.store_submitted_pings_enabled = enabled; + } + /// Enable or disable a ping. /// /// Disabling a ping causes all data for that ping to be removed from storage diff --git a/glean-core/src/database/sqlite.rs b/glean-core/src/database/sqlite.rs index 71454cf229..cc69edca82 100644 --- a/glean-core/src/database/sqlite.rs +++ b/glean-core/src/database/sqlite.rs @@ -25,9 +25,9 @@ use crate::common_metric_data::CommonMetricDataInternal; use crate::database::migration::{self, MigrationState}; use crate::metrics::dual_labeled_counter::RECORD_SEPARATOR; use crate::metrics::Metric; -use crate::Glean; use crate::Lifetime; use crate::Result; +use crate::{Glean, JsonValue}; use super::ConnExt; @@ -83,7 +83,7 @@ pub struct SubmittedPing { } impl SubmittedPing { - pub fn value(&self) -> Option { + pub fn value(&self) -> Option { self.value .as_ref() .map(|v| rmp_serde::from_slice(v).expect("IMPOSSIBLE: Deserializing value failed")) @@ -309,6 +309,7 @@ impl Database { let conn = self.conn.lock(); let conn = &*conn; + self.cleanup_submitted_pings(Some(conn), None)?; self.run_maintenance_vacuum(conn, force)?; self.run_maintenance_optimize(conn)?; self.run_maintenance_checkpoint(conn)?; @@ -490,6 +491,7 @@ impl Database { .unwrap_or(false) } + /// Gets all pings in the `submitted_pings` table. pub fn get_all_submitted_pings(&self) -> Vec { let get_all_submitted_pings_sql = r#" SELECT @@ -525,6 +527,11 @@ impl Database { .unwrap_or_default() } + /// Returns all submitted pings in the `submitted_pings` table that match a supplied ping name. + /// + /// # Arguments + /// + /// * `ping` - The name of the pings to return. pub fn get_submitted_pings(&self, ping: &str) -> Vec { let get_submitted_pings_sql = r#" SELECT @@ -562,6 +569,16 @@ impl Database { .unwrap_or_default() } + /// Marks a particular ping as uploaded. + /// + /// # Arguments + /// + /// * `document_id` - The ping to mark as uploaded. + /// * `date_uploaded` - The UTC date/time the ping was uploaded. + /// + /// # Returns + /// + /// A `usize` representing the number of rows updated. pub fn mark_ping_as_uploaded(&self, document_id: &str, date_uploaded: DateTime) -> usize { let get_submitted_pings_sql = r#" UPDATE submitted_pings @@ -578,13 +595,26 @@ impl Database { .unwrap_or_default() } + /// Stores a submitted ping into the `submitted_pings` table. + /// + /// # Arguments + /// + /// * `document_id` - The unique identifier for the ping. + /// * `ping` - The name of the ping. + /// * `date_submitted` - The UTC date/time the ping was submitted. + /// * `date_uploaded` - An optional UTC date/time the ping was uploaded. + /// * `value` - A JSON representation of the content of the ping. + /// + /// # Returns + /// + /// An empty `Result`. pub fn store_submitted_ping( &self, document_id: &str, ping: &str, date_submitted: DateTime, date_uploaded: Option>, - value: serde_json::Value, + value: JsonValue, ) -> Result<()> { self.conn.write(|tx| { let insert_sql = r#" @@ -611,6 +641,38 @@ impl Database { }) } + /// Removes rows from the `submitted_pings` table where the `date_submitted` is older than the supplied date. + /// + /// # Arguments + /// + /// * `before_time` - An optional date – when supplied uses that date as the oldest date_submitted we should keep. + /// Defaults to 30 days if `None` is supplied. + /// + /// # Returns + /// + /// An empty `Result`. + pub fn cleanup_submitted_pings( + &self, + conn: Option<&rusqlite::Connection>, + before_time: Option>, + ) -> Result<()> { + let time = before_time.unwrap_or(Utc::now() - Duration::from_secs(2592000)); + let delete_sql = r#" + DELETE FROM submitted_pings + WHERE date_submitted < ?1 + "#; + if let Some(conn) = conn { + let mut stmt = conn.prepare_cached(delete_sql)?; + stmt.execute(params![SqliteDatetime(time)])?; + } else { + self.conn.write(|tx| { + let mut stmt = tx.prepare_cached(delete_sql)?; + stmt.execute(params![SqliteDatetime(time)]) + })?; + } + Ok(()) + } + /// Records a metric in the underlying storage system. pub fn record(&self, glean: &Glean, data: &CommonMetricDataInternal, value: &Metric) { let name = data.base_identifier(); diff --git a/glean-core/src/lib_unit_tests.rs b/glean-core/src/lib_unit_tests.rs index 55bab5f58d..f85a431cef 100644 --- a/glean-core/src/lib_unit_tests.rs +++ b/glean-core/src/lib_unit_tests.rs @@ -238,6 +238,7 @@ fn experimentation_id_is_set_correctly() { session_sample_rate: 1.0, session_inactivity_timeout_ms: 1_800_000, events_ping_acceleration_factor: None, + enable_store_submitted_pings: false, }) .unwrap(); diff --git a/glean-core/src/upload/mod.rs b/glean-core/src/upload/mod.rs index 889ab3d1c2..20087c0d39 100644 --- a/glean-core/src/upload/mod.rs +++ b/glean-core/src/upload/mod.rs @@ -799,6 +799,11 @@ impl PingUploadManager { .set_stop_and_accumulate(glean, success_id, stop_time); self.upload_metrics.send_failure.cancel_sync(failure_id); } + if glean.store_submitted_pings_enabled { + glean + .storage() + .mark_ping_as_uploaded(document_id, Utc::now()); + } self.directory_manager.delete_file(document_id); } @@ -2128,4 +2133,53 @@ mod test { UploadResult::http_status(200), ); } + + #[test] + fn stores_pings_during_submission_and_upload_if_enabled() { + let (mut glean, _t) = new_glean(None); + glean.set_store_submitted_pings_enabled(true); + + // Register a ping for testing + let ping_type = PingType::new( + "test", + true, + /* send_if_empty */ true, + true, + true, + true, + vec![], + vec![], + true, + vec![], + ); + glean.register_ping_type(&ping_type); + + // Submit a ping + ping_type.submit_sync(&glean, None); + + let pings = glean.storage().get_all_submitted_pings(); + assert_eq!(pings.len(), 1); + let ping = pings.first().unwrap(); + assert!(ping.submitted_date.0 <= Utc::now()); + assert!(ping.uploaded_date.is_none()); + + // Get the submitted PingRequest + match glean.get_upload_task() { + PingUploadTask::Upload { request } => { + // Simulate the processing of a sucessful request + let document_id = request.document_id; + glean.process_ping_upload_response(&document_id, UploadResult::http_status(200)); + } + _ => panic!("Expected upload manager to return the next request!"), + } + + let pings = glean.storage().get_all_submitted_pings(); + assert_eq!(pings.len(), 1); + let ping = pings.first().unwrap(); + assert!(ping.submitted_date.0 <= Utc::now()); + assert!(ping.uploaded_date.is_some()); + + // Verify that after request is returned, none are left + assert_eq!(glean.get_upload_task(), PingUploadTask::done()); + } } diff --git a/glean-core/tests/sqlite.rs b/glean-core/tests/sqlite.rs index 0b4870daa0..ab9d6657b3 100644 --- a/glean-core/tests/sqlite.rs +++ b/glean-core/tests/sqlite.rs @@ -311,8 +311,8 @@ fn test_storing_and_fetching_submitted_pings() { glean .storage() .store_submitted_ping( - "id".into(), - "ping".into(), + "id", + "ping", utc_time_one, None, serde_json::json!({ "test": "a value" }), @@ -323,8 +323,8 @@ fn test_storing_and_fetching_submitted_pings() { glean .storage() .store_submitted_ping( - "id-one".into(), - "ping-two".into(), + "id-one", + "ping-two", utc_time_two, None, serde_json::json!({ "test": "a value" }), @@ -335,8 +335,8 @@ fn test_storing_and_fetching_submitted_pings() { glean .storage() .store_submitted_ping( - "id-one".into(), - "ping-two".into(), + "id-one", + "ping-two", utc_time_two, Some(utc_time_two), serde_json::json!({ "test": "a value" }), @@ -368,3 +368,62 @@ fn test_storing_and_fetching_submitted_pings() { utc_time_one ); } + +#[test] +fn test_cleanup_of_submitted_pings() { + let (glean, _temp) = new_glean(None); + + let utc_time_more_than_30_days_ago = + chrono::DateTime::parse_from_rfc3339("2026-06-05T12:30:00.50Z") + .unwrap() + .to_utc(); + + // Submitted ping from more than 30 days ago + glean + .storage() + .store_submitted_ping( + "id-one", + "ping", + utc_time_more_than_30_days_ago, + None, + serde_json::json!({ "test": "a value" }), + ) + .unwrap(); + + // Submitted ping from now + glean + .storage() + .store_submitted_ping( + "id-two", + "ping", + Utc::now(), + None, + serde_json::json!({ "test": "a value" }), + ) + .unwrap(); + + // Both pings should have been stored + let all_pings = glean.storage().get_all_submitted_pings(); + assert_eq!(all_pings.len(), 2); + + // Run regular maintenance (happens on shutdown) + // This should only remove the ping from >30 days ago + glean + .storage() + .run_maintenance(false) + .expect("run_maintenance failed"); + + let all_pings = glean.storage().get_all_submitted_pings(); + assert_eq!(all_pings.len(), 1); + assert_eq!(all_pings.first().unwrap().document_id, "id-two".to_string()); + + // Run `cleanup_submitted_pings` with now as the `before_time` + // This should clear out all pings + glean + .storage() + .cleanup_submitted_pings(None, Some(Utc::now())) + .expect("Error running cleanup_submitted_pings"); + + let all_pings = glean.storage().get_all_submitted_pings(); + assert_eq!(all_pings.len(), 0); +} From 9a576be853e1b7c13fbef500f1b56295e50b0b76 Mon Sep 17 00:00:00 2001 From: Charlie Humphreys Date: Thu, 13 Aug 2026 14:25:01 -0500 Subject: [PATCH 13/22] add variables and things to rlb --- glean-core/examples/rkv-open.rs | 1 + glean-core/rlb/src/configuration.rs | 12 ++++++++++++ glean-core/rlb/src/lib.rs | 8 ++++++++ glean-core/src/error.rs | 12 ------------ glean-core/tests/event.rs | 1 + glean-core/tests/ping.rs | 1 + glean-core/tests/ping_maker.rs | 2 ++ 7 files changed, 25 insertions(+), 12 deletions(-) diff --git a/glean-core/examples/rkv-open.rs b/glean-core/examples/rkv-open.rs index 3e72ac0d1e..e97a4e1c01 100644 --- a/glean-core/examples/rkv-open.rs +++ b/glean-core/examples/rkv-open.rs @@ -57,6 +57,7 @@ fn main() { session_sample_rate: 1.0, session_inactivity_timeout_ms: 1_800_000, events_ping_acceleration_factor: None, + enable_store_submitted_pings: false, }; let client_info = ClientInfoMetrics::unknown(); diff --git a/glean-core/rlb/src/configuration.rs b/glean-core/rlb/src/configuration.rs index fd10141b94..85e74bfa0d 100644 --- a/glean-core/rlb/src/configuration.rs +++ b/glean-core/rlb/src/configuration.rs @@ -67,6 +67,8 @@ pub struct Configuration { pub session_inactivity_timeout: Duration, /// The number of "events" pings to accelerate each session, plus one. pub events_ping_acceleration_factor: Option, + /// Whether to store submitted pings or not + pub enable_store_submitted_pings: bool, } /// Configuration builder. @@ -131,6 +133,8 @@ pub struct Builder { pub session_inactivity_timeout: Duration, /// The number of "events" pings to accelerate each session, plus one. pub events_ping_acceleration_factor: Option, + /// Whether to store submitted pings or not. + pub enable_store_submitted_pings: bool, } impl Builder { @@ -162,6 +166,7 @@ impl Builder { session_sample_rate: 1.0, session_inactivity_timeout: Duration::from_secs(30 * 60), events_ping_acceleration_factor: None, + enable_store_submitted_pings: false, } } @@ -189,6 +194,7 @@ impl Builder { session_sample_rate: self.session_sample_rate, session_inactivity_timeout: self.session_inactivity_timeout, events_ping_acceleration_factor: self.events_ping_acceleration_factor, + enable_store_submitted_pings: self.enable_store_submitted_pings, } } @@ -293,4 +299,10 @@ impl Builder { self.events_ping_acceleration_factor = Some(factor); self } + + /// Set whether to store submitted pings or not. + pub fn with_store_submitted_pings_enabled(mut self, value: bool) -> Self { + self.enable_store_submitted_pings = value; + self + } } diff --git a/glean-core/rlb/src/lib.rs b/glean-core/rlb/src/lib.rs index 17ddd84d3b..10291851c8 100644 --- a/glean-core/rlb/src/lib.rs +++ b/glean-core/rlb/src/lib.rs @@ -136,6 +136,7 @@ fn initialize_internal(cfg: Configuration, client_info: ClientInfoMetrics) -> Op session_sample_rate: cfg.session_sample_rate, session_inactivity_timeout_ms: cfg.session_inactivity_timeout.as_millis() as u64, events_ping_acceleration_factor: cfg.events_ping_acceleration_factor.map(|x| x as u32), + enable_store_submitted_pings: cfg.enable_store_submitted_pings, }; glean_core::glean_initialize(core_cfg, client_info.into(), callbacks); @@ -180,6 +181,13 @@ pub fn set_collection_enabled(enabled: bool) { glean_core::glean_set_collection_enabled(enabled) } +/// Sets whether storing submitted pings is enabled or not. +/// +/// See [`glean_core::Glean::set_store_submitted_pings_enabled`]. +pub fn set_store_submitted_pings_enabled(enabled: bool) { + glean_core::glean_set_store_submitted_pings_enabled(enabled) +} + /// Collects and submits a ping for eventual uploading by name. /// /// Note that this needs to be public in order for RLB consumers to diff --git a/glean-core/src/error.rs b/glean-core/src/error.rs index a0a6de88d3..622ab3893f 100644 --- a/glean-core/src/error.rs +++ b/glean-core/src/error.rs @@ -73,9 +73,6 @@ pub enum ErrorKind { /// Schema error Schema(SchemaError), - - /// Chrono parse error - Chrono(chrono::ParseError), } /// A specialized [`Error`] type for this crate's operations. @@ -134,7 +131,6 @@ impl Display for Error { UuidError(e) => write!(f, "Failed to parse UUID: {}", e), SQLite(e) => write!(f, "SQLite error: {}", e), Schema(e) => write!(f, "Schema error: {}", e), - Chrono(e) => write!(f, "Chrono time parse error: {}", e), } } } @@ -217,14 +213,6 @@ impl From for Error { } } -impl From for Error { - fn from(error: chrono::ParseError) -> Self { - Error { - kind: ErrorKind::Chrono(error), - } - } -} - #[derive(Debug)] pub enum ClientIdFileError { /// The file could not be found. diff --git a/glean-core/tests/event.rs b/glean-core/tests/event.rs index 0205a4a807..4b0183805a 100644 --- a/glean-core/tests/event.rs +++ b/glean-core/tests/event.rs @@ -559,6 +559,7 @@ fn with_event_timestamps() { session_sample_rate: 1.0, session_inactivity_timeout_ms: 1_800_000, events_ping_acceleration_factor: None, + enable_store_submitted_pings: false, }; let mut glean = Glean::new(cfg).unwrap(); let ping = PingBuilder::new("store1").build(); diff --git a/glean-core/tests/ping.rs b/glean-core/tests/ping.rs index 884f25d158..7a7015441b 100644 --- a/glean-core/tests/ping.rs +++ b/glean-core/tests/ping.rs @@ -389,6 +389,7 @@ fn clearing_storage_by_prefix_doesnt_clear_unrelated_delayed_ping_io() { session_sample_rate: 1.0, session_inactivity_timeout_ms: 1_800_000, events_ping_acceleration_factor: None, + enable_store_submitted_pings: false, }; let mut glean = glean_core::Glean::new(cfg).unwrap(); diff --git a/glean-core/tests/ping_maker.rs b/glean-core/tests/ping_maker.rs index 0bc84b3aed..75d1be3204 100644 --- a/glean-core/tests/ping_maker.rs +++ b/glean-core/tests/ping_maker.rs @@ -102,6 +102,7 @@ fn test_metrics_must_report_experimentation_id() { session_sample_rate: 1.0, session_inactivity_timeout_ms: 1_800_000, events_ping_acceleration_factor: None, + enable_store_submitted_pings: false, }) .unwrap(); let ping_maker = PingMaker::new(); @@ -163,6 +164,7 @@ fn experimentation_id_is_removed_if_send_if_empty_is_false() { session_sample_rate: 1.0, session_inactivity_timeout_ms: 1_800_000, events_ping_acceleration_factor: None, + enable_store_submitted_pings: false, }) .unwrap(); let ping_maker = PingMaker::new(); From cff6f4cb2aeaffbf852d6b2f69f4950c6c622591 Mon Sep 17 00:00:00 2001 From: Charlie Humphreys Date: Thu, 13 Aug 2026 15:04:46 -0500 Subject: [PATCH 14/22] set default value for enable_store_submitted_pings in InternalConfiguration UDL --- glean-core/src/database/sqlite.rs | 1 + glean-core/src/glean.udl | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/glean-core/src/database/sqlite.rs b/glean-core/src/database/sqlite.rs index cc69edca82..33c9cbdfde 100644 --- a/glean-core/src/database/sqlite.rs +++ b/glean-core/src/database/sqlite.rs @@ -645,6 +645,7 @@ impl Database { /// /// # Arguments /// + /// * `conn` - An optional rusqlite connection. If not supplied, it will open a `write` using `self.conn`. /// * `before_time` - An optional date – when supplied uses that date as the oldest date_submitted we should keep. /// Defaults to 30 days if `None` is supplied. /// diff --git a/glean-core/src/glean.udl b/glean-core/src/glean.udl index 6291b09977..581e970270 100644 --- a/glean-core/src/glean.udl +++ b/glean-core/src/glean.udl @@ -129,7 +129,7 @@ dictionary InternalConfiguration { f64 session_sample_rate; // Must be in [0.0, 1.0]; values outside are clamped. u64 session_inactivity_timeout_ms; // Milliseconds; 0 means sessions never time out. u32? events_ping_acceleration_factor; - boolean enable_store_submitted_pings; + boolean enable_store_submitted_pings = false; }; // Session management mode. From 4a7abb389ddcd474a9ff123fbea421b4d5255cda Mon Sep 17 00:00:00 2001 From: Charlie Humphreys Date: Thu, 13 Aug 2026 15:20:04 -0500 Subject: [PATCH 15/22] update based on feedback --- glean-core/src/database/sqlite.rs | 53 +++++++++++++++---------------- glean-core/src/lib.rs | 3 ++ glean-core/src/upload/mod.rs | 3 ++ glean-core/tests/sqlite.rs | 6 ++-- 4 files changed, 35 insertions(+), 30 deletions(-) diff --git a/glean-core/src/database/sqlite.rs b/glean-core/src/database/sqlite.rs index 33c9cbdfde..ab74b54193 100644 --- a/glean-core/src/database/sqlite.rs +++ b/glean-core/src/database/sqlite.rs @@ -90,7 +90,7 @@ impl SubmittedPing { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct SqliteDatetime(pub DateTime); impl ToSql for SqliteDatetime { @@ -108,12 +108,6 @@ impl FromSql for SqliteDatetime { } } -impl PartialEq for SqliteDatetime { - fn eq(&self, other: &Self) -> bool { - self.0 == other.0 - } -} - const DEFAULT_DATABASE_FILE_NAME: &str = "glean.sqlite"; /// Calculate the database size from all the files in the directory. @@ -309,7 +303,6 @@ impl Database { let conn = self.conn.lock(); let conn = &*conn; - self.cleanup_submitted_pings(Some(conn), None)?; self.run_maintenance_vacuum(conn, force)?; self.run_maintenance_optimize(conn)?; self.run_maintenance_checkpoint(conn)?; @@ -580,14 +573,14 @@ impl Database { /// /// A `usize` representing the number of rows updated. pub fn mark_ping_as_uploaded(&self, document_id: &str, date_uploaded: DateTime) -> usize { - let get_submitted_pings_sql = r#" + let update_submitted_pings_sql = r#" UPDATE submitted_pings SET date_uploaded = ?1 WHERE document_id = ?2 "#; self.conn - .write(|conn| { - let Ok(mut stmt) = conn.prepare_cached(get_submitted_pings_sql) else { + .write(|tx| { + let Ok(mut stmt) = tx.prepare_cached(update_submitted_pings_sql) else { return Ok(Default::default()); }; stmt.execute(params![SqliteDatetime(date_uploaded), document_id]) @@ -595,6 +588,22 @@ impl Database { .unwrap_or_default() } + pub fn mark_ping_as_upload_failed(&self, document_id: &str) -> usize { + let update_submitted_pings_sql = r#" + UPDATE submitted_pings + SET upload_failed = true + WHERE document_id = ?1 + "#; + self.conn + .write(|tx| { + let Ok(mut stmt) = tx.prepare_cached(update_submitted_pings_sql) else { + return Ok(Default::default()); + }; + stmt.execute(params![document_id]) + }) + .unwrap_or_default() + } + /// Stores a submitted ping into the `submitted_pings` table. /// /// # Arguments @@ -645,32 +654,22 @@ impl Database { /// /// # Arguments /// - /// * `conn` - An optional rusqlite connection. If not supplied, it will open a `write` using `self.conn`. /// * `before_time` - An optional date – when supplied uses that date as the oldest date_submitted we should keep. /// Defaults to 30 days if `None` is supplied. /// /// # Returns /// /// An empty `Result`. - pub fn cleanup_submitted_pings( - &self, - conn: Option<&rusqlite::Connection>, - before_time: Option>, - ) -> Result<()> { + pub fn cleanup_submitted_pings(&self, before_time: Option>) -> Result<()> { let time = before_time.unwrap_or(Utc::now() - Duration::from_secs(2592000)); let delete_sql = r#" DELETE FROM submitted_pings - WHERE date_submitted < ?1 + WHERE date_submitted <= ?1 "#; - if let Some(conn) = conn { - let mut stmt = conn.prepare_cached(delete_sql)?; - stmt.execute(params![SqliteDatetime(time)])?; - } else { - self.conn.write(|tx| { - let mut stmt = tx.prepare_cached(delete_sql)?; - stmt.execute(params![SqliteDatetime(time)]) - })?; - } + self.conn.write(|tx| { + let mut stmt = tx.prepare_cached(delete_sql)?; + stmt.execute(params![SqliteDatetime(time)]) + })?; Ok(()) } diff --git a/glean-core/src/lib.rs b/glean-core/src/lib.rs index 4f1f87c430..30403a0adf 100644 --- a/glean-core/src/lib.rs +++ b/glean-core/src/lib.rs @@ -797,6 +797,9 @@ pub fn shutdown() { } if let Some(database) = &glean.data_store { + if let Err(e) = database.cleanup_submitted_pings(None) { + log::info!("Could not clean up submitted_pings table: {:?}", e); + } if let Err(e) = database.run_maintenance(false) { log::info!("Can't run database maintenance on shutdown: {:?}", e); } diff --git a/glean-core/src/upload/mod.rs b/glean-core/src/upload/mod.rs index 20087c0d39..f1c0cb08de 100644 --- a/glean-core/src/upload/mod.rs +++ b/glean-core/src/upload/mod.rs @@ -819,6 +819,9 @@ impl PingUploadManager { .send_failure .set_stop_and_accumulate(glean, failure_id, stop_time); } + if glean.store_submitted_pings_enabled { + glean.storage().mark_ping_as_upload_failed(document_id); + } self.directory_manager.delete_file(document_id); } diff --git a/glean-core/tests/sqlite.rs b/glean-core/tests/sqlite.rs index ab9d6657b3..16c26f499f 100644 --- a/glean-core/tests/sqlite.rs +++ b/glean-core/tests/sqlite.rs @@ -410,8 +410,8 @@ fn test_cleanup_of_submitted_pings() { // This should only remove the ping from >30 days ago glean .storage() - .run_maintenance(false) - .expect("run_maintenance failed"); + .cleanup_submitted_pings(None) + .expect("Error running cleanup_submitted_pings"); let all_pings = glean.storage().get_all_submitted_pings(); assert_eq!(all_pings.len(), 1); @@ -421,7 +421,7 @@ fn test_cleanup_of_submitted_pings() { // This should clear out all pings glean .storage() - .cleanup_submitted_pings(None, Some(Utc::now())) + .cleanup_submitted_pings(Some(Utc::now())) .expect("Error running cleanup_submitted_pings"); let all_pings = glean.storage().get_all_submitted_pings(); From 7c4958dbfd62241123066ea9cf0e93093c57c2f2 Mon Sep 17 00:00:00 2001 From: Charlie Humphreys Date: Fri, 14 Aug 2026 08:04:49 -0500 Subject: [PATCH 16/22] uv.lock changes --- samples/glean-sym-test/uv.lock | 172 +++++++++++++++++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 samples/glean-sym-test/uv.lock diff --git a/samples/glean-sym-test/uv.lock b/samples/glean-sym-test/uv.lock new file mode 100644 index 0000000000..5473cd8a0a --- /dev/null +++ b/samples/glean-sym-test/uv.lock @@ -0,0 +1,172 @@ +version = 1 +revision = 3 +requires-python = ">=3.9" +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version < '3.10'", +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, +] + +[[package]] +name = "glean-sym-sample" +version = "0.0.0" +source = { virtual = "." } +dependencies = [ + { name = "pytest" }, +] + +[package.metadata] +requires-dist = [{ name = "pytest", specifier = "==8.4.2" }] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "packaging" +version = "26.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, +] + +[[package]] +name = "pytest" +version = "8.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "iniconfig", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, +] + +[[package]] +name = "tomli" +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" }, + { url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" }, + { url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" }, + { url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" }, + { url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" }, + { url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" }, + { url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" }, + { url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" }, + { url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" }, + { url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" }, + { url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" }, + { url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" }, + { url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" }, + { url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" }, + { url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" }, + { url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" }, + { url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" }, + { url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" }, + { url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" }, + { url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" }, + { url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" }, + { url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" }, + { url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" }, + { url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" }, + { url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" }, + { url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" }, + { url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" }, + { url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" }, + { url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" }, + { url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" }, + { url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" }, + { url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" }, + { url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" }, + { url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" }, + { url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" }, + { url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" }, + { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, +] From 4b6ea9778f0c43f47dbf3c22ecdce6f43d21960f Mon Sep 17 00:00:00 2001 From: Charlie Humphreys Date: Fri, 14 Aug 2026 08:12:23 -0500 Subject: [PATCH 17/22] update glean-sym-test Cargo.lock --- samples/glean-sym-test/Cargo.lock | 49 --------- samples/glean-sym-test/uv.lock | 172 ------------------------------ 2 files changed, 221 deletions(-) delete mode 100644 samples/glean-sym-test/uv.lock diff --git a/samples/glean-sym-test/Cargo.lock b/samples/glean-sym-test/Cargo.lock index 62d63db7b2..7a2c532ed0 100644 --- a/samples/glean-sym-test/Cargo.lock +++ b/samples/glean-sym-test/Cargo.lock @@ -313,12 +313,6 @@ dependencies = [ "num_cpus", ] -[[package]] -name = "deranged" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" - [[package]] name = "displaydoc" version = "0.2.5" @@ -870,12 +864,6 @@ dependencies = [ "minimal-lexical", ] -[[package]] -name = "num-conv" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" - [[package]] name = "num-traits" version = "0.2.19" @@ -969,12 +957,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - [[package]] name = "prettyplease" version = "0.2.37" @@ -1091,7 +1073,6 @@ dependencies = [ "hashlink", "libsqlite3-sys", "smallvec", - "time", ] [[package]] @@ -1318,36 +1299,6 @@ dependencies = [ "syn", ] -[[package]] -name = "time" -version = "0.3.51" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85c17d80feb7334b40c484e45ed1a5273dfd8bfda537c3be2e74a06a6686f327" -dependencies = [ - "deranged", - "num-conv", - "powerfmt", - "serde_core", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" - -[[package]] -name = "time-macros" -version = "0.2.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcef1a61bdb119096e153208ec5cbec23944ce8bca13be5c7f60c634f7403935" -dependencies = [ - "num-conv", - "time-core", -] - [[package]] name = "tinystr" version = "0.8.3" diff --git a/samples/glean-sym-test/uv.lock b/samples/glean-sym-test/uv.lock deleted file mode 100644 index 5473cd8a0a..0000000000 --- a/samples/glean-sym-test/uv.lock +++ /dev/null @@ -1,172 +0,0 @@ -version = 1 -revision = 3 -requires-python = ">=3.9" -resolution-markers = [ - "python_full_version >= '3.10'", - "python_full_version < '3.10'", -] - -[[package]] -name = "colorama" -version = "0.4.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, -] - -[[package]] -name = "exceptiongroup" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, -] - -[[package]] -name = "glean-sym-sample" -version = "0.0.0" -source = { virtual = "." } -dependencies = [ - { name = "pytest" }, -] - -[package.metadata] -requires-dist = [{ name = "pytest", specifier = "==8.4.2" }] - -[[package]] -name = "iniconfig" -version = "2.1.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, -] - -[[package]] -name = "iniconfig" -version = "2.3.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.10'", -] -sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, -] - -[[package]] -name = "packaging" -version = "26.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" }, -] - -[[package]] -name = "pluggy" -version = "1.6.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, -] - -[[package]] -name = "pygments" -version = "2.20.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, -] - -[[package]] -name = "pytest" -version = "8.4.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, - { name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "iniconfig", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "packaging" }, - { name = "pluggy" }, - { name = "pygments" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, -] - -[[package]] -name = "tomli" -version = "2.4.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" }, - { url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" }, - { url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" }, - { url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" }, - { url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" }, - { url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" }, - { url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" }, - { url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" }, - { url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" }, - { url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" }, - { url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" }, - { url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" }, - { url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" }, - { url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" }, - { url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" }, - { url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" }, - { url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" }, - { url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" }, - { url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" }, - { url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" }, - { url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" }, - { url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" }, - { url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" }, - { url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" }, - { url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" }, - { url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" }, - { url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" }, - { url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" }, - { url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" }, - { url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" }, - { url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" }, - { url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" }, - { url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" }, - { url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" }, - { url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" }, - { url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" }, - { url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" }, - { url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" }, - { url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" }, - { url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" }, - { url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" }, - { url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" }, - { url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" }, - { url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" }, - { url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" }, - { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, -] - -[[package]] -name = "typing-extensions" -version = "4.16.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, -] From ddd3c53438e47dcbad43c0e0e729bd91ae37ea94 Mon Sep 17 00:00:00 2001 From: Charlie Humphreys Date: Fri, 14 Aug 2026 09:50:19 -0500 Subject: [PATCH 18/22] add method to lib to clear the stored submitted pings # Conflicts: # glean-core/benchmark/benches/dispatcher.rs # glean-core/benchmark/benches/lifetime_buffering.rs --- glean-core/benchmark/benches/dispatcher.rs | 1 + glean-core/benchmark/benches/lifetime_buffering.rs | 5 ++++- glean-core/src/glean.udl | 2 ++ glean-core/src/lib.rs | 12 ++++++++++++ 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/glean-core/benchmark/benches/dispatcher.rs b/glean-core/benchmark/benches/dispatcher.rs index f23881afbd..4f82c675c8 100644 --- a/glean-core/benchmark/benches/dispatcher.rs +++ b/glean-core/benchmark/benches/dispatcher.rs @@ -89,6 +89,7 @@ pub fn metric_dispatcher_benchmark(c: &mut Criterion) { session_sample_rate: 1.0, session_inactivity_timeout_ms: 1_800_000, events_ping_acceleration_factor: None, + enable_store_submitted_pings: false, }; let client_info = ClientInfoMetrics::unknown(); diff --git a/glean-core/benchmark/benches/lifetime_buffering.rs b/glean-core/benchmark/benches/lifetime_buffering.rs index cd31f80b95..76c4208bd7 100644 --- a/glean-core/benchmark/benches/lifetime_buffering.rs +++ b/glean-core/benchmark/benches/lifetime_buffering.rs @@ -4,7 +4,7 @@ //! Benchmark the impact of `delay_ping_lifetime_io` and automatic flushing on the overall performance. -use criterion::{Criterion, criterion_group, criterion_main}; +use criterion::{criterion_group, criterion_main, Criterion}; use glean_core::{CommonMetricData, CounterMetric, Glean, Lifetime}; pub fn delay_io_benchmark(c: &mut Criterion) { @@ -37,6 +37,7 @@ pub fn delay_io_benchmark(c: &mut Criterion) { session_sample_rate: 1.0, session_inactivity_timeout_ms: 1_800_000, events_ping_acceleration_factor: None, + enable_store_submitted_pings: false, }; let glean = Glean::new(cfg).unwrap(); @@ -85,6 +86,7 @@ pub fn delay_io_benchmark(c: &mut Criterion) { session_sample_rate: 1.0, session_inactivity_timeout_ms: 1_800_000, events_ping_acceleration_factor: None, + enable_store_submitted_pings: false, }; let glean = Glean::new(cfg).unwrap(); @@ -133,6 +135,7 @@ pub fn delay_io_benchmark(c: &mut Criterion) { session_sample_rate: 1.0, session_inactivity_timeout_ms: 1_800_000, events_ping_acceleration_factor: None, + enable_store_submitted_pings: false, }; let glean = Glean::new(cfg).unwrap(); diff --git a/glean-core/src/glean.udl b/glean-core/src/glean.udl index 581e970270..820e8ee3aa 100644 --- a/glean-core/src/glean.udl +++ b/glean-core/src/glean.udl @@ -29,6 +29,8 @@ namespace glean { void glean_set_store_submitted_pings_enabled(boolean enabled); + void glean_clear_stored_submitted_pings(); + // Experiment reporting API void glean_set_experiment_active(string experiment_id, string branch, record extra); void glean_set_experiment_inactive(string experiment_id); diff --git a/glean-core/src/lib.rs b/glean-core/src/lib.rs index 30403a0adf..3704b31dad 100644 --- a/glean-core/src/lib.rs +++ b/glean-core/src/lib.rs @@ -981,6 +981,18 @@ pub fn glean_set_store_submitted_pings_enabled(enabled: bool) { }); } +/// Clears the stored submitted pings. +pub fn glean_clear_stored_submitted_pings() { + launch_with_glean(|glean| { + if let Err(e) = glean + .storage() + .cleanup_submitted_pings(Some(chrono::Utc::now())) + { + log::warn!("Unable to clear stored submitted pings: {:?}", e); + } + }); +} + /// Enable or disable a ping. /// /// Disabling a ping causes all data for that ping to be removed from storage From af309104bfbdd294f5864343aaea18ae4b6dddc8 Mon Sep 17 00:00:00 2001 From: Charlie Humphreys Date: Fri, 14 Aug 2026 11:01:53 -0500 Subject: [PATCH 19/22] update based on feedback --- glean-core/src/database/sqlite.rs | 64 +++++++++++++----------- glean-core/src/database/sqlite/schema.rs | 7 +-- glean-core/src/metrics/ping.rs | 3 +- glean-core/src/upload/mod.rs | 50 ++++++++++++++++++ glean-core/tests/sqlite.rs | 34 ++++++++++--- 5 files changed, 119 insertions(+), 39 deletions(-) diff --git a/glean-core/src/database/sqlite.rs b/glean-core/src/database/sqlite.rs index ab74b54193..cb02c94dfc 100644 --- a/glean-core/src/database/sqlite.rs +++ b/glean-core/src/database/sqlite.rs @@ -79,14 +79,22 @@ pub struct SubmittedPing { pub ping: String, pub submitted_date: SqliteDatetime, pub uploaded_date: Option, - pub value: Option>, + pub upload_failed: bool, + pub payload: Option, } impl SubmittedPing { - pub fn value(&self) -> Option { - self.value + pub fn payload(&self) -> Option { + self.payload .as_ref() - .map(|v| rmp_serde::from_slice(v).expect("IMPOSSIBLE: Deserializing value failed")) + .map(|p| match serde_json::from_str(p) { + Ok(v) => Some(v), + Err(e) => { + log::warn!("Unable to serialize JSON payload from string: {:?}", e); + None + } + }) + .unwrap_or(None) } } @@ -492,6 +500,7 @@ impl Database { ping, date_submitted, date_uploaded, + upload_failed, payload FROM submitted_pings ORDER BY date_submitted DESC @@ -512,7 +521,8 @@ impl Database { ping: r.get(1).unwrap(), submitted_date: r.get(2).unwrap(), uploaded_date: r.get(3).unwrap(), - value: r.get(4).unwrap(), + upload_failed: r.get(4).unwrap(), + payload: r.get(5).unwrap(), }) }) .collect() @@ -525,13 +535,14 @@ impl Database { /// # Arguments /// /// * `ping` - The name of the pings to return. - pub fn get_submitted_pings(&self, ping: &str) -> Vec { + pub fn get_submitted_pings_by_name(&self, ping: &str) -> Vec { let get_submitted_pings_sql = r#" SELECT document_id, ping, date_submitted, date_uploaded, + upload_failed, payload FROM submitted_pings WHERE @@ -554,7 +565,8 @@ impl Database { ping: r.get(1).unwrap(), submitted_date: r.get(2).unwrap(), uploaded_date: r.get(3).unwrap(), - value: r.get(4).unwrap(), + upload_failed: r.get(4).unwrap(), + payload: r.get(5).unwrap(), }) }) .collect() @@ -573,11 +585,8 @@ impl Database { /// /// A `usize` representing the number of rows updated. pub fn mark_ping_as_uploaded(&self, document_id: &str, date_uploaded: DateTime) -> usize { - let update_submitted_pings_sql = r#" - UPDATE submitted_pings - SET date_uploaded = ?1 - WHERE document_id = ?2 - "#; + let update_submitted_pings_sql = + "UPDATE submitted_pings SET date_uploaded = ?1 WHERE document_id = ?2"; self.conn .write(|tx| { let Ok(mut stmt) = tx.prepare_cached(update_submitted_pings_sql) else { @@ -589,11 +598,8 @@ impl Database { } pub fn mark_ping_as_upload_failed(&self, document_id: &str) -> usize { - let update_submitted_pings_sql = r#" - UPDATE submitted_pings - SET upload_failed = true - WHERE document_id = ?1 - "#; + let update_submitted_pings_sql = + "UPDATE submitted_pings SET upload_failed = 1 WHERE document_id = ?1"; self.conn .write(|tx| { let Ok(mut stmt) = tx.prepare_cached(update_submitted_pings_sql) else { @@ -612,7 +618,7 @@ impl Database { /// * `ping` - The name of the ping. /// * `date_submitted` - The UTC date/time the ping was submitted. /// * `date_uploaded` - An optional UTC date/time the ping was uploaded. - /// * `value` - A JSON representation of the content of the ping. + /// * `payload` - A JSON representation of the content of the ping. /// /// # Returns /// @@ -623,34 +629,36 @@ impl Database { ping: &str, date_submitted: DateTime, date_uploaded: Option>, - value: JsonValue, + upload_failed: bool, + payload: JsonValue, ) -> Result<()> { self.conn.write(|tx| { let insert_sql = r#" INSERT INTO - submitted_pings (document_id, ping, date_submitted, date_uploaded, payload) + submitted_pings (document_id, ping, date_submitted, date_uploaded, upload_failed, payload) VALUES - (?1, ?2, ?3, ?4, ?5) + (?1, ?2, ?3, ?4, ?5, ?6) ON CONFLICT(document_id) DO UPDATE SET ping = excluded.ping, date_submitted = excluded.date_submitted, date_uploaded = excluded.date_uploaded, + upload_failed = excluded.upload_failed, payload = excluded.payload "#; let mut stmt = tx.prepare_cached(insert_sql)?; - let encoded = rmp_serde::to_vec(&value).expect("IMPOSSIBLE: Serializing metric failed"); stmt.execute(params![ document_id, ping, SqliteDatetime(date_submitted), date_uploaded.map(SqliteDatetime), - encoded + upload_failed, + serde_json::to_string(&payload).expect("Unable to convert JSON payload to string.") ])?; Ok(()) }) } - /// Removes rows from the `submitted_pings` table where the `date_submitted` is older than the supplied date. + /// Remove stored submitted pings that are older than `before_time` (or 30 days if not specified) /// /// # Arguments /// @@ -661,11 +669,9 @@ impl Database { /// /// An empty `Result`. pub fn cleanup_submitted_pings(&self, before_time: Option>) -> Result<()> { - let time = before_time.unwrap_or(Utc::now() - Duration::from_secs(2592000)); - let delete_sql = r#" - DELETE FROM submitted_pings - WHERE date_submitted <= ?1 - "#; + let days_30 = Duration::from_secs(30 * 24 * 60 * 60); + let time = before_time.unwrap_or_else(|| Utc::now() - days_30); + let delete_sql = "DELETE FROM submitted_pings WHERE date_submitted <= ?1"; self.conn.write(|tx| { let mut stmt = tx.prepare_cached(delete_sql)?; stmt.execute(params![SqliteDatetime(time)]) diff --git a/glean-core/src/database/sqlite/schema.rs b/glean-core/src/database/sqlite/schema.rs index 5e74989de9..175016d1a9 100644 --- a/glean-core/src/database/sqlite/schema.rs +++ b/glean-core/src/database/sqlite/schema.rs @@ -67,7 +67,8 @@ impl ConnectionOpener for Schema { ping TEXT NOT NULL, date_submitted INTEGER NOT NULL, date_uploaded INTEGER, - payload BLOB + upload_failed BOOLEAN NOT NULL, + payload TEXT ); CREATE INDEX submitted_pings_ping on submitted_pings(ping); ", @@ -108,12 +109,12 @@ impl ConnectionOpener for Schema { ping TEXT NOT NULL, date_submitted INTEGER NOT NULL, date_uploaded INTEGER, - payload BLOB + upload_failed BOOLEAN NOT NULL, + payload TEXT ); CREATE INDEX submitted_pings_ping on submitted_pings(ping); ", )?; - tx.execute("INSERT INTO migration (id, state) VALUES (2, 'done') ON CONFLICT(id) DO UPDATE SET state = excluded.state", [])?; Ok(()) } to_version => Err(SchemaError::UnsupportedSchemaVersion(to_version)), diff --git a/glean-core/src/metrics/ping.rs b/glean-core/src/metrics/ping.rs index 12e917ba10..e10a0ddb7d 100644 --- a/glean-core/src/metrics/ping.rs +++ b/glean-core/src/metrics/ping.rs @@ -359,9 +359,10 @@ impl PingType { &self.0.name, Utc::now(), None, + false, ping.content.clone(), ) { - log::warn!("{}", e); + log::warn!("Storing a submitted ping failed: {e}"); } } diff --git a/glean-core/src/upload/mod.rs b/glean-core/src/upload/mod.rs index f1c0cb08de..7199a5848e 100644 --- a/glean-core/src/upload/mod.rs +++ b/glean-core/src/upload/mod.rs @@ -2185,4 +2185,54 @@ mod test { // Verify that after request is returned, none are left assert_eq!(glean.get_upload_task(), PingUploadTask::done()); } + + #[test] + fn stores_pings_during_submission_and_marks_as_upload_failed_when_appropriate() { + let (mut glean, _t) = new_glean(None); + glean.set_store_submitted_pings_enabled(true); + + // Register a ping for testing + let ping_type = PingType::new( + "test", + true, + /* send_if_empty */ true, + true, + true, + true, + vec![], + vec![], + true, + vec![], + ); + glean.register_ping_type(&ping_type); + + // Submit a ping + ping_type.submit_sync(&glean, None); + + let pings = glean.storage().get_all_submitted_pings(); + assert_eq!(pings.len(), 1); + let ping = pings.first().unwrap(); + assert!(ping.submitted_date.0 <= Utc::now()); + assert!(ping.uploaded_date.is_none()); + + // Get the submitted PingRequest + match glean.get_upload_task() { + PingUploadTask::Upload { request } => { + // Simulate the processing of a sucessful request + let document_id = request.document_id; + glean.process_ping_upload_response(&document_id, UploadResult::http_status(400)); + } + _ => panic!("Expected upload manager to return the next request!"), + } + + let pings = glean.storage().get_all_submitted_pings(); + assert_eq!(pings.len(), 1); + let ping = pings.first().unwrap(); + assert!(ping.submitted_date.0 <= Utc::now()); + assert!(ping.upload_failed); + assert!(ping.uploaded_date.is_none()); + + // Verify that after request is returned, none are left + assert_eq!(glean.get_upload_task(), PingUploadTask::done()); + } } diff --git a/glean-core/tests/sqlite.rs b/glean-core/tests/sqlite.rs index 16c26f499f..086011374b 100644 --- a/glean-core/tests/sqlite.rs +++ b/glean-core/tests/sqlite.rs @@ -306,6 +306,9 @@ fn test_storing_and_fetching_submitted_pings() { let utc_time_two = chrono::DateTime::parse_from_rfc3339("2026-08-05T12:30:00.51Z") .unwrap() .to_utc(); + let utc_time_three = chrono::DateTime::parse_from_rfc3339("2026-08-05T12:30:00.52Z") + .unwrap() + .to_utc(); // First ping, no upload date glean @@ -315,6 +318,7 @@ fn test_storing_and_fetching_submitted_pings() { "ping", utc_time_one, None, + false, serde_json::json!({ "test": "a value" }), ) .unwrap(); @@ -327,6 +331,7 @@ fn test_storing_and_fetching_submitted_pings() { "ping-two", utc_time_two, None, + false, serde_json::json!({ "test": "a value" }), ) .unwrap(); @@ -339,28 +344,43 @@ fn test_storing_and_fetching_submitted_pings() { "ping-two", utc_time_two, Some(utc_time_two), + false, + serde_json::json!({ "test": "a value" }), + ) + .unwrap(); + + // Third ping, upload failed + glean + .storage() + .store_submitted_ping( + "id-two", + "ping-three", + utc_time_three, + None, + true, serde_json::json!({ "test": "a value" }), ) .unwrap(); let all_pings = glean.storage().get_all_submitted_pings(); - assert_eq!(all_pings.len(), 2); - assert_eq!(all_pings.first().unwrap().document_id, "id-one".to_string()); + assert_eq!(all_pings.len(), 3); assert_eq!(all_pings.last().unwrap().document_id, "id".to_string()); - assert_eq!(all_pings.first().unwrap().submitted_date.0, utc_time_two); + assert_eq!(all_pings.get(1).unwrap().document_id, "id-one".to_string()); + assert_eq!(all_pings.get(1).unwrap().submitted_date.0, utc_time_two); assert_eq!( - all_pings.first().unwrap().uploaded_date.clone().unwrap().0, + all_pings.get(1).unwrap().uploaded_date.clone().unwrap().0, utc_time_two ); assert_eq!( - all_pings.first().unwrap().value().unwrap(), + all_pings.get(1).unwrap().payload().unwrap(), serde_json::json!({ "test": "a value" }) ); + assert!(all_pings.first().unwrap().upload_failed); let count = glean.storage().mark_ping_as_uploaded("id", utc_time_one); assert_eq!(count, 1); - let some_pings = glean.storage().get_submitted_pings("ping"); + let some_pings = glean.storage().get_submitted_pings_by_name("ping"); assert_eq!(some_pings.len(), 1); assert_eq!(some_pings.first().unwrap().document_id, "id".to_string()); assert_eq!( @@ -386,6 +406,7 @@ fn test_cleanup_of_submitted_pings() { "ping", utc_time_more_than_30_days_ago, None, + false, serde_json::json!({ "test": "a value" }), ) .unwrap(); @@ -398,6 +419,7 @@ fn test_cleanup_of_submitted_pings() { "ping", Utc::now(), None, + false, serde_json::json!({ "test": "a value" }), ) .unwrap(); From 296d68e7da8296e168af525e7c4b741444543556 Mon Sep 17 00:00:00 2001 From: Charlie Humphreys Date: Fri, 14 Aug 2026 11:44:52 -0500 Subject: [PATCH 20/22] add FFI/RLB methods to retrieve stored submitted pings from database --- glean-core/rlb/src/lib.rs | 23 +++++++++++++-- glean-core/src/database/sqlite.rs | 1 - glean-core/src/glean.udl | 13 ++++++++ glean-core/src/lib.rs | 49 +++++++++++++++++++++++++++++++ 4 files changed, 83 insertions(+), 3 deletions(-) diff --git a/glean-core/rlb/src/lib.rs b/glean-core/rlb/src/lib.rs index 10291851c8..63feda7e9b 100644 --- a/glean-core/rlb/src/lib.rs +++ b/glean-core/rlb/src/lib.rs @@ -182,12 +182,31 @@ pub fn set_collection_enabled(enabled: bool) { } /// Sets whether storing submitted pings is enabled or not. -/// -/// See [`glean_core::Glean::set_store_submitted_pings_enabled`]. pub fn set_store_submitted_pings_enabled(enabled: bool) { glean_core::glean_set_store_submitted_pings_enabled(enabled) } +/// Returns all stored submitted pings. +/// +/// Requires storing submitted pings to be enabled. +/// See [`set_store_submitted_pings_enabled`]. +pub fn get_all_stored_submitted_pings() -> Vec { + glean_core::glean_get_all_stored_submitted_pings() +} + +/// Returns all stored submitted pings with a given ping name. +/// +/// Requires storing submitted pings to be enabled. +/// See [`set_store_submitted_pings_enabled`]. +pub fn get_stored_submitted_pings_by_name(ping: String) -> Vec { + glean_core::glean_get_stored_submitted_pings_by_name(ping) +} + +/// Clears all stored submitted pings. +pub fn clear_stored_submitted_pings() { + glean_core::glean_clear_stored_submitted_pings() +} + /// Collects and submits a ping for eventual uploading by name. /// /// Note that this needs to be public in order for RLB consumers to diff --git a/glean-core/src/database/sqlite.rs b/glean-core/src/database/sqlite.rs index cb02c94dfc..d055dcd367 100644 --- a/glean-core/src/database/sqlite.rs +++ b/glean-core/src/database/sqlite.rs @@ -73,7 +73,6 @@ impl MallocSizeOf for Database { } } -#[allow(unused)] pub struct SubmittedPing { pub document_id: String, pub ping: String, diff --git a/glean-core/src/glean.udl b/glean-core/src/glean.udl index 820e8ee3aa..eea5e205f2 100644 --- a/glean-core/src/glean.udl +++ b/glean-core/src/glean.udl @@ -29,6 +29,10 @@ namespace glean { void glean_set_store_submitted_pings_enabled(boolean enabled); + sequence glean_get_all_stored_submitted_pings(); + + sequence glean_get_stored_submitted_pings_by_name(string ping); + void glean_clear_stored_submitted_pings(); // Experiment reporting API @@ -99,6 +103,15 @@ namespace glean { DistributionMetrics glean_test_get_distribution(); }; +dictionary SubmittedPing { + string document_id; + string ping; + string submitted_date; + string? uploaded_date; + boolean upload_failed; + JsonValue? payload; +}; + // A `Cow<'static, str>`, but really it's always the owned part. [Custom] typedef string CowString; diff --git a/glean-core/src/lib.rs b/glean-core/src/lib.rs index 3704b31dad..1d72fb21be 100644 --- a/glean-core/src/lib.rs +++ b/glean-core/src/lib.rs @@ -981,6 +981,55 @@ pub fn glean_set_store_submitted_pings_enabled(enabled: bool) { }); } +/// A submitted ping that has been stored by Glean. +pub struct SubmittedPing { + /// The document ID (unique identifier) + document_id: String, + /// The ping's name + ping: String, + /// RFC3339 datetime string + submitted_date: String, + /// Optional RFC3339 datetime string + uploaded_date: Option, + /// Whether the upload failed unrecoverably or not + upload_failed: bool, + /// The ping's payload + payload: Option, +} + +impl From for SubmittedPing { + fn from(value: database::sqlite::SubmittedPing) -> Self { + SubmittedPing { + document_id: value.document_id.clone(), + ping: value.ping.clone(), + submitted_date: value.submitted_date.0.to_rfc3339(), + uploaded_date: value.uploaded_date.as_ref().map(|d| d.0.to_rfc3339()), + upload_failed: value.upload_failed, + payload: value.payload(), + } + } +} + +/// Returns a `Vec` containing all stored submitted pings. +pub fn glean_get_all_stored_submitted_pings() -> Vec { + core::with_glean(|glean| glean.storage().get_all_submitted_pings()) + .into_iter() + .map(|p| p.into()) + .collect() +} + +/// Returns a `Vec` containing all stored submitted pings with the supplied name. +/// +/// # Arguments +/// +/// * `ping` - The name of the pings that should be returned. +pub fn glean_get_stored_submitted_pings_by_name(ping: String) -> Vec { + core::with_glean(|glean| glean.storage().get_submitted_pings_by_name(&ping)) + .into_iter() + .map(|p| p.into()) + .collect() +} + /// Clears the stored submitted pings. pub fn glean_clear_stored_submitted_pings() { launch_with_glean(|glean| { From 0f436f4a1f7608094051abb1ca0055f782887839 Mon Sep 17 00:00:00 2001 From: Charlie Humphreys Date: Fri, 14 Aug 2026 13:13:22 -0500 Subject: [PATCH 21/22] update changelog --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 048e90bd82..638b00c7c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ # Unreleased changes -* Add new `submitted_pings` table to the SQLite database. Also added methods to store submitted pings and retrieve them ([#3585](https://github.com/mozilla/glean/pull/3585)). +* Add mechanisms for storing and retrieving submitted pings ([#3585](https://github.com/mozilla/glean/pull/3585)). + * Add new `submitted_pings` table to the SQLite database. + * Add methods to store, retrieve, update, and clear stored submitted pings. + * Update Ping and uploader implementations to store and update submitted pings as appropriate. [Full changelog](https://github.com/mozilla/glean/compare/v70.0.0...main) From 02263b738eb3a41d5233a4d70f0b234440d0295d Mon Sep 17 00:00:00 2001 From: Charlie Humphreys Date: Tue, 1 Sep 2026 08:53:28 -0500 Subject: [PATCH 22/22] fix formatting --- glean-core/src/core/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glean-core/src/core/mod.rs b/glean-core/src/core/mod.rs index 9c5ece8a72..0d250528ae 100644 --- a/glean-core/src/core/mod.rs +++ b/glean-core/src/core/mod.rs @@ -281,7 +281,7 @@ impl Glean { events_ping_acceleration_factor: cfg .events_ping_acceleration_factor .map(|x| x as usize), - store_submitted_pings_enabled: cfg.enable_store_submitted_pings + store_submitted_pings_enabled: cfg.enable_store_submitted_pings, }; // Ensuring these pings are registered.