commit a71c51bcb81ca1b32f60b36510338cc0ee2e82fa parent 0cbcfd42039b19c0c62ae74cb233d22cf136564c Author: Jan-Erik Rediger <jrediger@mozilla.com> Date: Tue, 4 Nov 2025 11:12:06 +0000 Bug 1997923 - Update to Glean v66.1.0 r=chutten,supply-chain-reviewers,mach-reviewers,ahal Differential Revision: https://phabricator.services.mozilla.com/D271096 Diffstat:
33 files changed, 1111 insertions(+), 120 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock @@ -2714,9 +2714,9 @@ dependencies = [ [[package]] name = "glean" -version = "66.0.0" +version = "66.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acc348bbf540abeedf7c271daf952b1e24311e2ea1c1f8ac172a5cedf37ac91" +checksum = "48821b3640793b8922f8fcc1b2294f1ffd4eec8f5145c30efea229482360727f" dependencies = [ "crossbeam-channel", "glean-core", @@ -2729,9 +2729,9 @@ dependencies = [ [[package]] name = "glean-core" -version = "66.0.0" +version = "66.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "540e40007a01e43b73904d1ee8f7efd4782f1a7f4a20ef1dd6d2c00fc18d54b6" +checksum = "6f2e01e857cf2ca46dfcaa1228289adec3ceefe59350e9194dc87edac4ce6eda" dependencies = [ "android_logger", "bincode", diff --git a/Cargo.toml b/Cargo.toml @@ -72,7 +72,7 @@ uniffi_pipeline = "0.29.3" # Shared across multiple application-services consumers. rusqlite = "0.37" # Shared across multiple glean consumers. -glean = "=66.0.0" +glean = "=66.1.0" # Explicitly specify what our profiles use. The opt-level setting here is # a total fiction; see the setup of MOZ_RUST_DEFAULT_FLAGS for what the diff --git a/gfx/wr/Cargo.lock b/gfx/wr/Cargo.lock @@ -1232,9 +1232,9 @@ dependencies = [ [[package]] name = "glean" -version = "66.0.0" +version = "66.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acc348bbf540abeedf7c271daf952b1e24311e2ea1c1f8ac172a5cedf37ac91" +checksum = "48821b3640793b8922f8fcc1b2294f1ffd4eec8f5145c30efea229482360727f" dependencies = [ "crossbeam-channel", "glean-core", @@ -1247,9 +1247,9 @@ dependencies = [ [[package]] name = "glean-core" -version = "66.0.0" +version = "66.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "540e40007a01e43b73904d1ee8f7efd4782f1a7f4a20ef1dd6d2c00fc18d54b6" +checksum = "6f2e01e857cf2ca46dfcaa1228289adec3ceefe59350e9194dc87edac4ce6eda" dependencies = [ "android_logger", "bincode", diff --git a/gfx/wr/Cargo.toml b/gfx/wr/Cargo.toml @@ -10,7 +10,7 @@ resolver = "2" [workspace.dependencies] gleam = "0.15.1" -glean = "=66.0.0" +glean = "=66.1.0" [profile.release] debug = true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml @@ -76,7 +76,7 @@ ktlint = "1.7.1" spotless = "8.0.0" # Mozilla -glean = "66.0.0" +glean = "66.1.0" # Testing libraries androidx-test = "1.7.0" diff --git a/python/sites/mach.txt b/python/sites/mach.txt @@ -57,7 +57,7 @@ pth:xpcom/geckoprocesstypes_generator pth:xpcom/idl-parser # glean-sdk may not be installable if a wheel isn't available # and it has to be built from source. -pypi-optional:glean-sdk==66.0.0:telemetry will not be collected +pypi-optional:glean-sdk==66.1.0:telemetry will not be collected pypi-optional:orjson>=3.10.15:json operations will be slower in various tools # Mach gracefully handles the case where `psutil` is unavailable. # We aren't (yet) able to pin packages in automation, so we have to diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock @@ -261,15 +261,15 @@ user-login = "jrmuizel" user-name = "Jeff Muizelaar" [[publisher.glean]] -version = "66.0.0" -when = "2025-10-21" +version = "66.1.0" +when = "2025-11-03" user-id = 48 user-login = "badboy" user-name = "Jan-Erik Rediger" [[publisher.glean-core]] -version = "66.0.0" -when = "2025-10-21" +version = "66.1.0" +when = "2025-11-03" user-id = 48 user-login = "badboy" user-name = "Jan-Erik Rediger" @@ -994,12 +994,6 @@ publication of this crate from CI. This repository requires all PRs are reviewed by a Bytecode Alliance maintainer and it owned by the Bytecode Alliance itself. """ -[[audits.bytecode-alliance.audits.adler]] -who = "Alex Crichton <alex@alexcrichton.com>" -criteria = "safe-to-deploy" -version = "1.0.2" -notes = "This is a small crate which forbids unsafe code and is a straightforward implementation of the adler hashing algorithm." - [[audits.bytecode-alliance.audits.adler2]] who = "Alex Crichton <alex@alexcrichton.com>" criteria = "safe-to-deploy" diff --git a/third_party/rust/glean-core/.cargo-checksum.json b/third_party/rust/glean-core/.cargo-checksum.json @@ -1 +1 @@ -{"files":{"Cargo.lock":"8593755a9f74ae880223e47bea3a928558c9e99a9d24b3b02787d3dfd77b4a45","Cargo.toml":"7bdf81bfa0a1cf0f840ef231ae5c4e5a5cb136b5db2a8751a4993b558f418cbe","LICENSE":"1f256ecad192880510e84ad60474eab7589218784b9a50bc7ceee34c2b91f1d5","README.md":"026495898699b54608eb4ec16074ffafc57920d80ccb59961c501a1ea28c9985","build.rs":"4857bea99c6b8c08db8818efa9d3738716f52d3acb68159323957ae52892a3eb","examples/rkv-open.rs":"0d0aaaa29b47338880ab444cece0dfa212ff114cbaf738c9a4cd6e7f10545854","src/common_metric_data.rs":"571075e47241a5f21799994033f852d0d70d0e486661287646ac61dadd11ac59","src/core/mod.rs":"14141b42926a91ec4924e7246f3b891508a63bd1750330c4ad2198674b4d7d7d","src/core_metrics.rs":"ddc750e9f4e11f88aee947360897031657aeef74fcf0f3293bd9033b78747354","src/coverage.rs":"49613fd310bd24d779472720975fbe6c97ec370a95eb55f10afa43f67539c942","src/database/mod.rs":"c2de5ea95bbfd828cd3f2943fd7b9125cccbf7de79bb8564970ec2b1782f271a","src/debug.rs":"663f77a3335256a562afad031496ea9215e315edd608398452df514dcff0b4af","src/dispatcher/global.rs":"17493b1a5e926ca1e9c1cda2ee031773402a19183911eb80048630d60266d89e","src/dispatcher/mod.rs":"30086e2aa8b34d7bdeef6ca93c115f3f22c438dc7a9896ce8251c9e6992420ea","src/error.rs":"b93c7d3e243b21bb9eafc95f17860aba1a942b2f2b0a7f43307690f05fece516","src/error_recording.rs":"32c1f58ff080cb22184bca37e356faadbfe846386880fb0898b773adb3a97d8c","src/event_database/mod.rs":"cfd584864a8300a2b441bd91558dcc64ab8130bcc9e19583edeb37a3d9622d2c","src/fd_logger.rs":"0c9def6fa53db1a2ab93c85795f8a7df57797bcfd3978146923e151752e291a6","src/glean.udl":"43d66c7ee56533bdac9f272f3281515ee1e2cc1d9db2c85811f37283acda2967","src/glean_metrics.rs":"eb8dcf95b7a1a5c53d880f0ff791300e6c4c5e21f0761fed7c004381bc43a230","src/histogram/exponential.rs":"036d7ff1d19dc5cda7c491b839565b32901093af4091ec8f3d306495e4116a47","src/histogram/functional.rs":"fffa12a5b2b376cf9337ee0fdd6ad7961841e441b556a4e9ef29c524f933ea2d","src/histogram/linear.rs":"ef77f6a3177f9fb474b68392eab3b308ab9ec7f3ffc3d7e75939927f5939cff6","src/histogram/mod.rs":"b4033298bdd011542cf4243a4f64634b4b9b815aa0a7c2c662a55310298dab5e","src/internal_metrics.rs":"ed17121170c203062a9a8f0debb1da8caef49056e1a54096f53ffc3be7571075","src/internal_pings.rs":"308a0437325686cdbc7b7ed2f444a972e76a06d3982c4d9551324f8e21e96a23","src/lib.rs":"59074e53a3b670577da34a5a95d5f7a7b638cef3f7957a1c3c10901dd6e77b69","src/lib_unit_tests.rs":"cc0ca9d21ce75fc9e8b445240d770018459b8f71f44b08a988d4ac2d25ac3c4f","src/metrics/boolean.rs":"06c6f4a7c236083f95f3a9f2bc543aa2745f8d02d25bd2dee0dfe37ca2558662","src/metrics/counter.rs":"5cdebb985fc960283926f96d2e08b9e72a70c5d6e5e27463671a64d8eb532d84","src/metrics/custom_distribution.rs":"2801f3bb298df1ef306dee05baf03e29deb4d3d6bd8328deeb3d34bd337e4fff","src/metrics/datetime.rs":"f38db0aae52dd2eba726b76a9a246c70e15ed9dc303c3d1df33229b9fdc3826b","src/metrics/denominator.rs":"69403002f65cf6c2a792c17f1714e4841a44283e3df19704b84cd46554a4bd21","src/metrics/dual_labeled_counter.rs":"c2fa7faff470b0d78c1abfb17a6983a2a4f13ef9004430fa6649fe2ba0485f68","src/metrics/event.rs":"f79103475b656b631861431e7b943956d94b1a92621f2e05d6fbca0312673c20","src/metrics/experiment.rs":"5f9278cca4e133eb8df33bbfe36d1fe0ef3eade8c09f1b46db3c4d0790515412","src/metrics/labeled.rs":"6bab38ccd297f92b54b91f6a36c39a9eaecf7573f6f761850c467e605cd0dd2b","src/metrics/memory_distribution.rs":"681ee0a15ebdd7d7f81edac0ef7e8f1e7bb0eb45414b4a95a8c33b9f215ac444","src/metrics/memory_unit.rs":"ee32e020cb303dd631457374048a3ed53a2e7cbacc29c54d17d836fb15507538","src/metrics/mod.rs":"63bbefcc54b22a066a14da6850b3a7f6c8aea2102e64678d84c66bb65f682e5d","src/metrics/numerator.rs":"de8853a691980606745895c083c22036347117b3d556de66a9d9978ec0f51168","src/metrics/object.rs":"cae8f118350e5fd7d28a810ac8d00ba7e401237f3e4fbb6233eef3874ddf8683","src/metrics/ping.rs":"6eef8ca063adb1158a66bf283fa66c2349e1551366b1f027150a45f24025b364","src/metrics/quantity.rs":"a39359e4c21ffbe687580eeac02967f12dec85ba0461ac2167a5bbd23ab7b2af","src/metrics/rate.rs":"ffde9ace03310287c7d6624b391c2168571deaf421b7e1e71fe4b771867f6a36","src/metrics/recorded_experiment.rs":"6f089534bb4e1143abf0aa43e813c9fcc864fff128a825e83f09e61e96a1f6ee","src/metrics/remote_settings_config.rs":"3beea00afd2779f1e4d2d88ed43cbce1e5a62286ea74b52f0a62236f3b34c217","src/metrics/string.rs":"6469efc8b42841ad7dd265bb77c9ed187cb175e62e006a47cc46ec677b907715","src/metrics/string_list.rs":"acb68507cddc8f3a6a90abb075d1099fe047603f4af3c776de6fa25fc5893f57","src/metrics/text.rs":"ffb9664d8eadbc858da543d27e11c9a8aff8dadfa98f4b7b92e458a7627c2a48","src/metrics/time_unit.rs":"72325065e4ac0f3562f1983bda9fe3c0ab18272011dafe03d67900a3184a8eb6","src/metrics/timespan.rs":"028245d298c800a877f614bd8832fb596ff11ea600d4e00b4967c127cbb48994","src/metrics/timing_distribution.rs":"30af0e8417176b5ec3136bbefe699e3ec7c342c0a14dac3b27f35d2b55d853b7","src/metrics/url.rs":"3efa62c1c1070319f56fb641d61f489918d716763916dcd6d6d3073b3a6f8c6f","src/metrics/uuid.rs":"3b18c899d2c1e5967b73594dd2a0ab6f2b0c220eaf2bf3d4ef7de2cb1c1e4edd","src/ping/mod.rs":"22c85bd40fd0dbf479d504c6b8671833d02fbe2da2d2778fcbb11aeeea5f65b6","src/scheduler.rs":"2463d406492b43e3b4544bc3b6cdeea99421b2bb04178a0cf38cd9cfcd00c5e4","src/storage/mod.rs":"4c7daf0a8252b027577104a553bc5eeaf5a8b008e6109b249ed081c20b5720ea","src/system.rs":"e3d1b54e1d39cafe6f4dc7ff5021b08c879733f909951b0e1332b3efa9ed97bd","src/thread.rs":"f8504fb6cfc7b43566f232930d5075d8a759fd7339d81ff01ba73568298492c7","src/traits/boolean.rs":"fa955e81ee0ff1a3da86002347cf4713f4ccd4e7a32ecddc7e8eab6483820277","src/traits/counter.rs":"8ed21fad9163b10a4fec89c052437ada69bbda6c9fe9e912bd9a2531a3264427","src/traits/custom_distribution.rs":"6934948c7ca72d42d093f424ccfa6de7fef4b7b17a838c942ed6c4aa94cf222b","src/traits/datetime.rs":"6c3bae9b7e971181c4d0c39d873a424b4bd30bdab3dc9fd00027327902f628fd","src/traits/dual_labeled_counter.rs":"63cbed60be3e8d695788f0a3624507787a005a0aaf05bdd3828fdfb0890952e0","src/traits/event.rs":"f372bfce1247d59693af351f2d79023c07d4f2f1fbaa38fc0bdce1af6d218bb5","src/traits/labeled.rs":"1b2f8eea8d59e1635f64993936d880084a5e3f65cfd7a26ddf6968366890187d","src/traits/memory_distribution.rs":"9e559d2579092062da945ec1d901439b3907fdc9db2bb376b10f9a877b58f2ac","src/traits/mod.rs":"1ffab9c66d37a4759c369d805386792eb39f769d6a801e1737eb19cfdb40477f","src/traits/numerator.rs":"d5991ef6463d17127ce7c969f8a3e110ec83b59714162f04d371ac43844241a1","src/traits/object.rs":"c03bad670ec7affbc578247f9e1904e898c1870b9bf25750c5094113f995623f","src/traits/ping.rs":"8831c106c03afeb458b0b028fa1ce61f056ebf8e82bc0a171a1bff255d920748","src/traits/quantity.rs":"cff0f7fdd0ee41d0457d9ca08222f652b4649eb64e0c0fbdfa76849013012c56","src/traits/rate.rs":"4f0a3a68d78631e5d5b83cbc65bfb9a41359b4fd8874296d58adebbc914a739a","src/traits/string.rs":"594a330ca03b21128040a263b3d4c85f66aed5147ac785f036f8c537c691dfd3","src/traits/string_list.rs":"b0b46bdf8c48a7480a0144eb40bf93fbfd8086af8e5f73863676fb695fb443ba","src/traits/text.rs":"b700031c6204126e73d9ea9c36f59fe6eb0b3b5dc50696072445c4420806f80f","src/traits/timespan.rs":"95d747ade6a58428f613b8860289a369386f45a8dc2b76ba741e39cbf19cc438","src/traits/timing_distribution.rs":"20e3ff9eed201a782f35584c657af0c7430673a97afeae708e2ea893a5723b95","src/traits/url.rs":"36fa73762288c4d33dd72de1a47c1f5451d2d6e751cf293d5e0ec88ebd4f9a71","src/traits/uuid.rs":"ea31080e349166d59272a119a546870650fab3dc512d356efbc98a3254165023","src/upload/directory.rs":"b676a05ae19a937e81391d749048fd7f544c629999dd71c8198319b5534da956","src/upload/mod.rs":"0cabe040aa8711c419922bd2e00acf8e3d3af48a65f01c8be590316b957e876a","src/upload/policy.rs":"0f8e50db8942b75ca871764e3a250d3d382925f752b162c8ac160185c5f6a2d7","src/upload/request.rs":"6922ca8ca6944b414686234156b1d46bf9e6f4bf745b77ed7a767ad231970876","src/upload/result.rs":"c956266c54595d27d5e8b11abffe28fad5fea82995922618805b66cfdcc21d54","src/util.rs":"8fe16c1a1c89d9ca0fee425d81a5809c999092c0b7543d386a2b9a6d649ba130","tests/boolean.rs":"76d6014ff108cb6514d9bceb1b2b14749a55b09921f4595a5e30f1bd3546e9f0","tests/collection_enabled.rs":"05517ad50a05e97f73b658eb854561284db1588c7f1ec736055436a0b2b64d99","tests/common/mod.rs":"be651c204ea7fc8412959a29d03a5c6ed868e20f8c06733869063dcfb18d8405","tests/counter.rs":"4c59def10e64de0d7a0241dde0267fd02c828b38be8cc67fba90a76004ca6721","tests/custom_distribution.rs":"41c593a0b4561e21f29d1a5b948de964a866253c58ca76ffefebe370fca150e0","tests/datetime.rs":"51bf8ebea3f5b99d2e5cab1d0e2cd15d750a05c5002b9dbec614ce7dc9093cf4","tests/dual_labeled_counter.rs":"e95a5713ec3faa2cbd4bfdafa9b2f4051bb7c3d7e3fd09ca4d730d5ee7d30ffe","tests/event.rs":"2321b9b80460aeaeb3583da5d475de7c6e331e95033f38313a9018056850201b","tests/labeled.rs":"01ea8db75d774f6ca864f7cc772c98c21e570511cb034e0226127a2bae7b605f","tests/memory_distribution.rs":"3d9a5b8f99e03f783e3396e4320ee4dc974c895cfef2cec0b14d2a9a9f9b736f","tests/object.rs":"8c35676e04f6ccf54a28764700915e753fc0355bfa5d7804d72caba66fd564cd","tests/ping.rs":"eff68d3b47b04c58d8123214dd66e8d06d11f82ed3fd4f703224f84effb319bb","tests/ping_maker.rs":"e4b834336dc8114531746a3c47a8c28c7ae9831856c06d9c0ce26a1167e4fcfd","tests/quantity.rs":"55e7dca346fd1d27f0974b78ca3fb12427cb5da2ee637afc08a54f360f947361","tests/rate.rs":"3766230e31cc4ab960f916b4c2f9208cc8542ecea0749298197f547b745440e9","tests/storage.rs":"990dd1d13b9ffa8af0686977a6ac3502c6befb9eaa83649587e2660f51c596c9","tests/string.rs":"397fcfd27c25f0e81e2a40db3265b0d7dc0dd56b190319c1f86cb1c2c0ed4f9d","tests/string_list.rs":"34efa2afe3e89e6635f21ec9c80650d5816e35e9cb85163d894d600a5b3a4e3a","tests/test-rkv-cases.sh":"1df53d64ba03ff60b242afb79099fb1a5847a7d29b4b8f6e3fa8d75bdeff1d71","tests/text.rs":"c3449e0dd8d774a60f47e39752c16951b8723eeea11c6a01d7fa7d3536285773","tests/timespan.rs":"3d05739a93f3e0ea7264e8fdea876bd948714e5b44e82b8fd7c0218fdb597bb3","tests/timing_distribution.rs":"072ad950b2162e503150e411119b9a000573538bd2cff93c1c1a3616d1589ef4","tests/uuid.rs":"e0e58614319f5e973126f5b4e68d9289ccd6d65e428aca215bf7dcc8a0504889","uniffi.toml":"6ddc98b686b0925a81abd9d1c769e5c98ac29771b210a1c535931a46dec9a8e3"},"package":"540e40007a01e43b73904d1ee8f7efd4782f1a7f4a20ef1dd6d2c00fc18d54b6"} -\ No newline at end of file +{"files":{"Cargo.lock":"c57333cba832d389c8de76bbaeff4a6c805450964a7f9349f16b6f300612a0d0","Cargo.toml":"cd11b19a1eeca7848c2cedf9ad6a2560c62a09ffaf3c46bb4cc5f3ec3670eca3","LICENSE":"1f256ecad192880510e84ad60474eab7589218784b9a50bc7ceee34c2b91f1d5","README.md":"026495898699b54608eb4ec16074ffafc57920d80ccb59961c501a1ea28c9985","build.rs":"4857bea99c6b8c08db8818efa9d3738716f52d3acb68159323957ae52892a3eb","examples/rkv-open.rs":"0d0aaaa29b47338880ab444cece0dfa212ff114cbaf738c9a4cd6e7f10545854","src/common_metric_data.rs":"571075e47241a5f21799994033f852d0d70d0e486661287646ac61dadd11ac59","src/core/mod.rs":"ccf902058e25b2b09fa6b2a9e49c810ef6b5b93ea75678c6a1dadcfc4f7dda67","src/core_metrics.rs":"ddc750e9f4e11f88aee947360897031657aeef74fcf0f3293bd9033b78747354","src/coverage.rs":"49613fd310bd24d779472720975fbe6c97ec370a95eb55f10afa43f67539c942","src/database/mod.rs":"cdb513345466686c3c2aed1585e0463aa9522af438bd14b0ba3111f875a43966","src/debug.rs":"663f77a3335256a562afad031496ea9215e315edd608398452df514dcff0b4af","src/dispatcher/global.rs":"17493b1a5e926ca1e9c1cda2ee031773402a19183911eb80048630d60266d89e","src/dispatcher/mod.rs":"30086e2aa8b34d7bdeef6ca93c115f3f22c438dc7a9896ce8251c9e6992420ea","src/error.rs":"21b087a55727b57ec26a017bb80887c6656aa461e38e82d9ecc5ca734b8fce71","src/error_recording.rs":"32c1f58ff080cb22184bca37e356faadbfe846386880fb0898b773adb3a97d8c","src/event_database/mod.rs":"cfd584864a8300a2b441bd91558dcc64ab8130bcc9e19583edeb37a3d9622d2c","src/fd_logger.rs":"4de0eb3d50ce96669de0bb581a4a794bec2cc768f2cb5221a02e739709c098e5","src/glean.udl":"43d66c7ee56533bdac9f272f3281515ee1e2cc1d9db2c85811f37283acda2967","src/glean_metrics.rs":"eb8dcf95b7a1a5c53d880f0ff791300e6c4c5e21f0761fed7c004381bc43a230","src/histogram/exponential.rs":"101c16ef06f043c2bc8eb28684ab69c748b4bc8c6f66ed4e64ee24c0ee9a364e","src/histogram/functional.rs":"fffa12a5b2b376cf9337ee0fdd6ad7961841e441b556a4e9ef29c524f933ea2d","src/histogram/linear.rs":"9214b0c99884ce35969c397c336410d0a7e898f8cee697f6b80166b451dfa781","src/histogram/mod.rs":"b4033298bdd011542cf4243a4f64634b4b9b815aa0a7c2c662a55310298dab5e","src/internal_metrics.rs":"05c47eea0b3987f42b84726e48e19155f87df6c8423cfab32f491146f1470175","src/internal_pings.rs":"308a0437325686cdbc7b7ed2f444a972e76a06d3982c4d9551324f8e21e96a23","src/lib.rs":"550a70d4813aab66bbd693fd7e7af1842d91046fc6ee3ea62a8c1960b5cdd05f","src/lib_unit_tests.rs":"cc0ca9d21ce75fc9e8b445240d770018459b8f71f44b08a988d4ac2d25ac3c4f","src/metrics/boolean.rs":"06c6f4a7c236083f95f3a9f2bc543aa2745f8d02d25bd2dee0dfe37ca2558662","src/metrics/counter.rs":"5cdebb985fc960283926f96d2e08b9e72a70c5d6e5e27463671a64d8eb532d84","src/metrics/custom_distribution.rs":"48ec22cfa6b6481daa0152672e34f6793aab9c256192bdc3fce0f47f37c73f27","src/metrics/datetime.rs":"f38db0aae52dd2eba726b76a9a246c70e15ed9dc303c3d1df33229b9fdc3826b","src/metrics/denominator.rs":"69403002f65cf6c2a792c17f1714e4841a44283e3df19704b84cd46554a4bd21","src/metrics/dual_labeled_counter.rs":"8e1a4690f2a84d84aefbb211e3478565e440271fd53c31fae020b74e7d0ec0a7","src/metrics/event.rs":"f79103475b656b631861431e7b943956d94b1a92621f2e05d6fbca0312673c20","src/metrics/experiment.rs":"5f9278cca4e133eb8df33bbfe36d1fe0ef3eade8c09f1b46db3c4d0790515412","src/metrics/labeled.rs":"6bab38ccd297f92b54b91f6a36c39a9eaecf7573f6f761850c467e605cd0dd2b","src/metrics/memory_distribution.rs":"681ee0a15ebdd7d7f81edac0ef7e8f1e7bb0eb45414b4a95a8c33b9f215ac444","src/metrics/memory_unit.rs":"ee32e020cb303dd631457374048a3ed53a2e7cbacc29c54d17d836fb15507538","src/metrics/mod.rs":"63bbefcc54b22a066a14da6850b3a7f6c8aea2102e64678d84c66bb65f682e5d","src/metrics/numerator.rs":"de8853a691980606745895c083c22036347117b3d556de66a9d9978ec0f51168","src/metrics/object.rs":"cae8f118350e5fd7d28a810ac8d00ba7e401237f3e4fbb6233eef3874ddf8683","src/metrics/ping.rs":"6eef8ca063adb1158a66bf283fa66c2349e1551366b1f027150a45f24025b364","src/metrics/quantity.rs":"a39359e4c21ffbe687580eeac02967f12dec85ba0461ac2167a5bbd23ab7b2af","src/metrics/rate.rs":"ffde9ace03310287c7d6624b391c2168571deaf421b7e1e71fe4b771867f6a36","src/metrics/recorded_experiment.rs":"6f089534bb4e1143abf0aa43e813c9fcc864fff128a825e83f09e61e96a1f6ee","src/metrics/remote_settings_config.rs":"3beea00afd2779f1e4d2d88ed43cbce1e5a62286ea74b52f0a62236f3b34c217","src/metrics/string.rs":"6469efc8b42841ad7dd265bb77c9ed187cb175e62e006a47cc46ec677b907715","src/metrics/string_list.rs":"acb68507cddc8f3a6a90abb075d1099fe047603f4af3c776de6fa25fc5893f57","src/metrics/text.rs":"ffb9664d8eadbc858da543d27e11c9a8aff8dadfa98f4b7b92e458a7627c2a48","src/metrics/time_unit.rs":"72325065e4ac0f3562f1983bda9fe3c0ab18272011dafe03d67900a3184a8eb6","src/metrics/timespan.rs":"028245d298c800a877f614bd8832fb596ff11ea600d4e00b4967c127cbb48994","src/metrics/timing_distribution.rs":"30af0e8417176b5ec3136bbefe699e3ec7c342c0a14dac3b27f35d2b55d853b7","src/metrics/url.rs":"3efa62c1c1070319f56fb641d61f489918d716763916dcd6d6d3073b3a6f8c6f","src/metrics/uuid.rs":"3b18c899d2c1e5967b73594dd2a0ab6f2b0c220eaf2bf3d4ef7de2cb1c1e4edd","src/ping/mod.rs":"a21212c4ee31d8b60760f21b31bf320ab921aa16503cc94e2fbc2b70ea604e3f","src/scheduler.rs":"2463d406492b43e3b4544bc3b6cdeea99421b2bb04178a0cf38cd9cfcd00c5e4","src/storage/mod.rs":"4c7daf0a8252b027577104a553bc5eeaf5a8b008e6109b249ed081c20b5720ea","src/system.rs":"e3d1b54e1d39cafe6f4dc7ff5021b08c879733f909951b0e1332b3efa9ed97bd","src/thread.rs":"f8504fb6cfc7b43566f232930d5075d8a759fd7339d81ff01ba73568298492c7","src/traits/boolean.rs":"fa955e81ee0ff1a3da86002347cf4713f4ccd4e7a32ecddc7e8eab6483820277","src/traits/counter.rs":"8ed21fad9163b10a4fec89c052437ada69bbda6c9fe9e912bd9a2531a3264427","src/traits/custom_distribution.rs":"3dd516a7cf62c48371ee36e7390707e049bca3bf45fefca2d6cb0d33c501df17","src/traits/datetime.rs":"6c3bae9b7e971181c4d0c39d873a424b4bd30bdab3dc9fd00027327902f628fd","src/traits/dual_labeled_counter.rs":"63cbed60be3e8d695788f0a3624507787a005a0aaf05bdd3828fdfb0890952e0","src/traits/event.rs":"f372bfce1247d59693af351f2d79023c07d4f2f1fbaa38fc0bdce1af6d218bb5","src/traits/labeled.rs":"1b2f8eea8d59e1635f64993936d880084a5e3f65cfd7a26ddf6968366890187d","src/traits/memory_distribution.rs":"9e559d2579092062da945ec1d901439b3907fdc9db2bb376b10f9a877b58f2ac","src/traits/mod.rs":"1ffab9c66d37a4759c369d805386792eb39f769d6a801e1737eb19cfdb40477f","src/traits/numerator.rs":"d5991ef6463d17127ce7c969f8a3e110ec83b59714162f04d371ac43844241a1","src/traits/object.rs":"c03bad670ec7affbc578247f9e1904e898c1870b9bf25750c5094113f995623f","src/traits/ping.rs":"8831c106c03afeb458b0b028fa1ce61f056ebf8e82bc0a171a1bff255d920748","src/traits/quantity.rs":"cff0f7fdd0ee41d0457d9ca08222f652b4649eb64e0c0fbdfa76849013012c56","src/traits/rate.rs":"4f0a3a68d78631e5d5b83cbc65bfb9a41359b4fd8874296d58adebbc914a739a","src/traits/string.rs":"594a330ca03b21128040a263b3d4c85f66aed5147ac785f036f8c537c691dfd3","src/traits/string_list.rs":"b0b46bdf8c48a7480a0144eb40bf93fbfd8086af8e5f73863676fb695fb443ba","src/traits/text.rs":"b700031c6204126e73d9ea9c36f59fe6eb0b3b5dc50696072445c4420806f80f","src/traits/timespan.rs":"95d747ade6a58428f613b8860289a369386f45a8dc2b76ba741e39cbf19cc438","src/traits/timing_distribution.rs":"20e3ff9eed201a782f35584c657af0c7430673a97afeae708e2ea893a5723b95","src/traits/url.rs":"36fa73762288c4d33dd72de1a47c1f5451d2d6e751cf293d5e0ec88ebd4f9a71","src/traits/uuid.rs":"ea31080e349166d59272a119a546870650fab3dc512d356efbc98a3254165023","src/upload/directory.rs":"b676a05ae19a937e81391d749048fd7f544c629999dd71c8198319b5534da956","src/upload/mod.rs":"0cabe040aa8711c419922bd2e00acf8e3d3af48a65f01c8be590316b957e876a","src/upload/policy.rs":"df95dacdc32977f10d28b3677d19c074a38daef90193f8fe3994ada3e5bcf08f","src/upload/request.rs":"6922ca8ca6944b414686234156b1d46bf9e6f4bf745b77ed7a767ad231970876","src/upload/result.rs":"c956266c54595d27d5e8b11abffe28fad5fea82995922618805b66cfdcc21d54","src/util.rs":"8fe16c1a1c89d9ca0fee425d81a5809c999092c0b7543d386a2b9a6d649ba130","tests/boolean.rs":"76d6014ff108cb6514d9bceb1b2b14749a55b09921f4595a5e30f1bd3546e9f0","tests/clientid_textfile.rs":"58fd74675a7d7e97e4eee6ecfc4089346d88ad1b1382b53deb9675d6a4b5e329","tests/collection_enabled.rs":"05517ad50a05e97f73b658eb854561284db1588c7f1ec736055436a0b2b64d99","tests/common/mod.rs":"be651c204ea7fc8412959a29d03a5c6ed868e20f8c06733869063dcfb18d8405","tests/counter.rs":"4c59def10e64de0d7a0241dde0267fd02c828b38be8cc67fba90a76004ca6721","tests/custom_distribution.rs":"41c593a0b4561e21f29d1a5b948de964a866253c58ca76ffefebe370fca150e0","tests/datetime.rs":"51bf8ebea3f5b99d2e5cab1d0e2cd15d750a05c5002b9dbec614ce7dc9093cf4","tests/dual_labeled_counter.rs":"e95a5713ec3faa2cbd4bfdafa9b2f4051bb7c3d7e3fd09ca4d730d5ee7d30ffe","tests/event.rs":"2321b9b80460aeaeb3583da5d475de7c6e331e95033f38313a9018056850201b","tests/labeled.rs":"01ea8db75d774f6ca864f7cc772c98c21e570511cb034e0226127a2bae7b605f","tests/memory_distribution.rs":"3d9a5b8f99e03f783e3396e4320ee4dc974c895cfef2cec0b14d2a9a9f9b736f","tests/object.rs":"8c35676e04f6ccf54a28764700915e753fc0355bfa5d7804d72caba66fd564cd","tests/ping.rs":"eff68d3b47b04c58d8123214dd66e8d06d11f82ed3fd4f703224f84effb319bb","tests/ping_maker.rs":"e4b834336dc8114531746a3c47a8c28c7ae9831856c06d9c0ce26a1167e4fcfd","tests/quantity.rs":"55e7dca346fd1d27f0974b78ca3fb12427cb5da2ee637afc08a54f360f947361","tests/rate.rs":"3766230e31cc4ab960f916b4c2f9208cc8542ecea0749298197f547b745440e9","tests/storage.rs":"990dd1d13b9ffa8af0686977a6ac3502c6befb9eaa83649587e2660f51c596c9","tests/string.rs":"397fcfd27c25f0e81e2a40db3265b0d7dc0dd56b190319c1f86cb1c2c0ed4f9d","tests/string_list.rs":"34efa2afe3e89e6635f21ec9c80650d5816e35e9cb85163d894d600a5b3a4e3a","tests/test-rkv-cases.sh":"1df53d64ba03ff60b242afb79099fb1a5847a7d29b4b8f6e3fa8d75bdeff1d71","tests/text.rs":"c3449e0dd8d774a60f47e39752c16951b8723eeea11c6a01d7fa7d3536285773","tests/timespan.rs":"3d05739a93f3e0ea7264e8fdea876bd948714e5b44e82b8fd7c0218fdb597bb3","tests/timing_distribution.rs":"072ad950b2162e503150e411119b9a000573538bd2cff93c1c1a3616d1589ef4","tests/uuid.rs":"e0e58614319f5e973126f5b4e68d9289ccd6d65e428aca215bf7dcc8a0504889","uniffi.toml":"6ddc98b686b0925a81abd9d1c769e5c98ac29771b210a1c535931a46dec9a8e3"},"package":"6f2e01e857cf2ca46dfcaa1228289adec3ceefe59350e9194dc87edac4ce6eda"} +\ No newline at end of file diff --git a/third_party/rust/glean-core/Cargo.lock b/third_party/rust/glean-core/Cargo.lock @@ -316,7 +316,7 @@ dependencies = [ [[package]] name = "glean-core" -version = "66.0.0" +version = "66.1.0" dependencies = [ "android_logger", "bincode", diff --git a/third_party/rust/glean-core/Cargo.toml b/third_party/rust/glean-core/Cargo.toml @@ -13,7 +13,7 @@ edition = "2021" rust-version = "1.82" name = "glean-core" -version = "66.0.0" +version = "66.1.0" authors = [ "Jan-Erik Rediger <jrediger@mozilla.com>", "The Glean Team <glean-team@mozilla.com>", @@ -40,7 +40,7 @@ license = "MPL-2.0" repository = "https://github.com/mozilla/glean" [package.metadata.glean] -glean-parser = "18.0.2" +glean-parser = "18.0.6" [lib] name = "glean_core" @@ -55,6 +55,10 @@ name = "boolean" path = "tests/boolean.rs" [[test]] +name = "clientid_textfile" +path = "tests/clientid_textfile.rs" + +[[test]] name = "collection_enabled" path = "tests/collection_enabled.rs" diff --git a/third_party/rust/glean-core/src/core/mod.rs b/third_party/rust/glean-core/src/core/mod.rs @@ -3,6 +3,8 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. use std::collections::HashMap; +use std::fs::{self, File}; +use std::io::{self, Write}; use std::path::{Path, PathBuf}; use std::sync::atomic::{AtomicU8, Ordering}; use std::sync::{Arc, Mutex}; @@ -11,11 +13,15 @@ use std::time::Duration; use chrono::{DateTime, FixedOffset}; use malloc_size_of_derive::MallocSizeOf; use once_cell::sync::OnceCell; +use uuid::Uuid; use crate::database::Database; use crate::debug::DebugOptions; +use crate::error::ClientIdFileError; use crate::event_database::EventDatabase; -use crate::internal_metrics::{AdditionalMetrics, CoreMetrics, DatabaseMetrics, HealthMetrics}; +use crate::internal_metrics::{ + AdditionalMetrics, CoreMetrics, DatabaseMetrics, ExceptionState, HealthMetrics, +}; use crate::internal_pings::InternalPings; use crate::metrics::{ self, ExperimentMetric, Metric, MetricType, PingType, RecordedExperiment, RemoteSettingsConfig, @@ -30,6 +36,7 @@ use crate::{ GLEAN_SCHEMA_VERSION, GLEAN_VERSION, KNOWN_CLIENT_ID, }; +const CLIENT_ID_PLAIN_FILENAME: &str = "client_id.txt"; static GLEAN: OnceCell<Mutex<Glean>> = OnceCell::new(); pub fn global_glean() -> Option<&'static Mutex<Glean>> { @@ -269,6 +276,159 @@ impl Glean { ping_lifetime_max_time, )?); + // This code references different states from the "Client ID recovery" flowchart. + // See https://mozilla.github.io/glean/dev/core/internal/client_id_recovery.html for details. + + // We don't have the database yet when we first encounter the error, + // so we store it and apply it later. + // state (a) + let stored_client_id = match glean.client_id_from_file() { + Ok(id) if id == *KNOWN_CLIENT_ID => { + glean + .health_metrics + .file_read_error + .get("c0ffee-in-file") + .add_sync(&glean, 1); + None + } + Ok(id) => Some(id), + Err(ClientIdFileError::NotFound) => { + // That's ok, the file might just not exist yet. + None + } + Err(ClientIdFileError::PermissionDenied) => { + // state (b) + // Uhm ... who removed our permission? + glean + .health_metrics + .file_read_error + .get("permission-denied") + .add_sync(&glean, 1); + None + } + Err(ClientIdFileError::ParseError(e)) => { + // state (b) + log::trace!("reading cliend_id.txt. Could not parse into UUID: {e}"); + glean + .health_metrics + .file_read_error + .get("parse") + .add_sync(&glean, 1); + None + } + Err(ClientIdFileError::IoError(e)) => { + // state (b) + // We can't handle other IO errors (most couldn't occur on this operation anyway) + log::trace!("reading client_id.txt. Unexpected io error: {e}"); + glean + .health_metrics + .file_read_error + .get("io") + .add_sync(&glean, 1); + None + } + }; + + { + let data_store = glean.data_store.as_ref().unwrap(); + let db_load_sizes = data_store.load_sizes.as_ref().unwrap(); + let new_size = db_load_sizes.new.unwrap_or(0); + + // If we have a client ID on disk, we check the database + if let Some(stored_client_id) = stored_client_id { + // state (c) + if new_size <= 0 { + log::trace!("no database. database size={new_size}. stored_client_id={stored_client_id}"); + // state (d) + glean + .health_metrics + .recovered_client_id + .set_from_uuid_sync(&glean, stored_client_id); + glean + .health_metrics + .exception_state + .set_sync(&glean, ExceptionState::EmptyDb); + + // state (e) -- mitigation: store recovered client ID in DB + } else { + let db_client_id = glean + .core_metrics + .client_id + .get_value(&glean, Some("glean_client_info")); + + match db_client_id { + None => { + // state (f) + log::trace!("no client_id in DB. stored_client_id={stored_client_id}"); + glean + .health_metrics + .exception_state + .set_sync(&glean, ExceptionState::RegenDb); + + // state (e) -- mitigation: store recovered client ID in DB + } + Some(db_client_id) if db_client_id == *KNOWN_CLIENT_ID => { + // state (i) + log::trace!( + "c0ffee client_id in DB, stored_client_id={stored_client_id}" + ); + glean + .health_metrics + .recovered_client_id + .set_from_uuid_sync(&glean, stored_client_id); + glean + .health_metrics + .exception_state + .set_sync(&glean, ExceptionState::C0ffeeInDb); + + // If we have a recovered client ID we also overwrite the database. + // state (e) + } + Some(db_client_id) if db_client_id == stored_client_id => { + // all valid. nothing to do + log::trace!("database consistent. db_client_id == stored_client_id: {db_client_id}"); + } + Some(db_client_id) => { + // state (g) + log::trace!( + "client_id mismatch. db_client_id{db_client_id}, stored_client_id={stored_client_id}. Overwriting file with db's client_id." + ); + glean + .health_metrics + .recovered_client_id + .set_from_uuid_sync(&glean, stored_client_id); + glean + .health_metrics + .exception_state + .set_sync(&glean, ExceptionState::ClientIdMismatch); + + // state (h) + glean.store_client_id_with_reporting( + db_client_id, + "client_id mismatch will re-occur.", + ); + } + } + } + } else { + log::trace!("No stored client ID. Database might have it."); + + let db_client_id = glean + .core_metrics + .client_id + .get_value(&glean, Some("glean_client_info")); + if let Some(db_client_id) = db_client_id { + // state (h) + glean.store_client_id_with_reporting( + db_client_id, + "Might happen on next init then.", + ); + } else { + log::trace!("Database has no client ID either. We might be fresh!"); + } + } + } + // Set experimentation identifier (if any) if let Some(experimentation_id) = &cfg.experimentation_id { glean @@ -296,6 +456,9 @@ impl Glean { { None => glean.clear_metrics(), Some(uuid) => { + if let Err(e) = glean.remove_stored_client_id() { + log::error!("Couldn't remove client ID on disk. This might lead to a resurrection of this client ID later. Error: {e}"); + } if uuid == *KNOWN_CLIENT_ID { // Previously Glean kept the KNOWN_CLIENT_ID stored. // Let's ensure we erase it now. @@ -373,6 +536,83 @@ impl Glean { self.data_store = None; } + fn client_id_file_path(&self) -> PathBuf { + self.data_path.join(CLIENT_ID_PLAIN_FILENAME) + } + + /// Write the client ID to a separate plain file on disk + /// + /// Use `store_client_id_with_reporting` to handle the error cases. + fn store_client_id(&self, client_id: Uuid) -> Result<(), ClientIdFileError> { + let mut fp = File::create(self.client_id_file_path())?; + + let mut buffer = Uuid::encode_buffer(); + let uuid_str = client_id.hyphenated().encode_lower(&mut buffer); + fp.write_all(uuid_str.as_bytes())?; + fp.sync_all()?; + + Ok(()) + } + + /// Write the client ID to a separate plain file on disk + /// + /// When an error occurs an error message is logged and the error is counted in a metric. + fn store_client_id_with_reporting(&self, client_id: Uuid, msg: &str) { + if let Err(err) = self.store_client_id(client_id) { + log::error!( + "Could not write {client_id} to state file. {} Error: {err}", + msg + ); + match err { + ClientIdFileError::NotFound => { + self.health_metrics + .file_write_error + .get("not-found") + .add_sync(self, 1); + } + ClientIdFileError::PermissionDenied => { + self.health_metrics + .file_write_error + .get("permission-denied") + .add_sync(self, 1); + } + ClientIdFileError::IoError(..) => { + self.health_metrics + .file_write_error + .get("io") + .add_sync(self, 1); + } + ClientIdFileError::ParseError(..) => { + log::error!("Parse error encountered on file write. This is impossible."); + } + } + } + } + + /// Try to load a client ID from the plain file on disk. + fn client_id_from_file(&self) -> Result<Uuid, ClientIdFileError> { + let uuid_str = fs::read_to_string(self.client_id_file_path())?; + // We don't write a newline, but we still trim it. Who knows who else touches that file by accident. + // We're also a bit more lenient in what we accept here: + // uppercase, lowercase, with or without dashes, urn, braced (and whatever else `Uuid` + // parses by default). + let uuid = Uuid::try_parse(uuid_str.trim_end())?; + Ok(uuid) + } + + /// Remove the stored client ID from disk. + /// Should only be called when the client ID is also removed from the database. + fn remove_stored_client_id(&self) -> Result<(), ClientIdFileError> { + match fs::remove_file(self.client_id_file_path()) { + Ok(()) => Ok(()), + Err(e) if e.kind() == io::ErrorKind::NotFound => { + // File was already missing. No need to report that. + Ok(()) + } + Err(e) => Err(e.into()), + } + } + /// Initializes the core metrics managed by Glean's Rust core. fn initialize_core_metrics(&mut self) { let need_new_client_id = match self @@ -384,7 +624,8 @@ impl Glean { Some(uuid) => uuid == *KNOWN_CLIENT_ID, }; if need_new_client_id { - self.core_metrics.client_id.generate_and_set_sync(self); + let new_clientid = self.core_metrics.client_id.generate_and_set_sync(self); + self.store_client_id_with_reporting(new_clientid, "New client in database only."); } if self @@ -619,6 +860,10 @@ impl Glean { log::warn!("Error clearing pending pings: {}", err); } + if let Err(e) = self.remove_stored_client_id() { + log::error!("Couldn't remove client ID on disk. This might lead to a resurrection of this client ID later. Error: {e}"); + } + // Delete all stored metrics. // Note that this also includes the ping sequence numbers, so it has // the effect of resetting those to their initial values. @@ -938,7 +1183,7 @@ impl Glean { /// Return the value for the debug view tag or [`None`] if it hasn't been set. /// /// The `debug_view_tag` may be set from an environment variable - /// (`GLEAN_DEBUG_VIEW_TAG`) or through the `set_debug_view_tag` function. + /// (`GLEAN_DEBUG_VIEW_TAG`) or through the [`set_debug_view_tag`](Glean::set_debug_view_tag) function. pub fn debug_view_tag(&self) -> Option<&String> { self.debug.debug_view_tag.get() } @@ -961,7 +1206,7 @@ impl Glean { /// Return the value for the source tags or [`None`] if it hasn't been set. /// /// The `source_tags` may be set from an environment variable (`GLEAN_SOURCE_TAGS`) - /// or through the [`set_source_tags`] function. + /// or through the [`set_source_tags`](Glean::set_source_tags) function. pub(crate) fn source_tags(&self) -> Option<&Vec<String>> { self.debug.source_tags.get() } diff --git a/third_party/rust/glean-core/src/database/mod.rs b/third_party/rust/glean-core/src/database/mod.rs @@ -147,7 +147,7 @@ pub struct Database { pub(crate) write_timings: RefCell<Vec<i64>>, /// The database size at various phases of loading. - load_sizes: Option<LoadSizesObject>, + pub(crate) load_sizes: Option<LoadSizesObject>, } impl MallocSizeOf for Database { @@ -187,7 +187,7 @@ impl std::fmt::Debug for Database { } } -/// Calculate the database size from all the files in the directory. +/// Calculate the database size from all the files in the directory. /// /// # Arguments /// diff --git a/third_party/rust/glean-core/src/error.rs b/third_party/rust/glean-core/src/error.rs @@ -62,6 +62,9 @@ pub enum ErrorKind { /// Ping request body size overflowed PingBodyOverflow(usize), + + /// Parsing a UUID from a string failed + UuidError(uuid::Error), } /// A specialized [`Error`] type for this crate's operations. @@ -117,6 +120,7 @@ impl Display for Error { "Ping request body size exceeded maximum size allowed: {}kB.", s / 1024 ), + UuidError(e) => write!(f, "Failed to parse UUID: {}", e), } } } @@ -167,3 +171,54 @@ impl From<std::convert::Infallible> for Error { unreachable!() } } + +impl From<uuid::Error> for Error { + fn from(error: uuid::Error) -> Self { + Error { + kind: ErrorKind::UuidError(error), + } + } +} + +#[derive(Debug)] +pub enum ClientIdFileError { + /// The file could not be found. + NotFound, + /// Can't access the file due to permissions + PermissionDenied, + /// Another io error happened + IoError(io::Error), + /// Parsing the content into a UUID failed + ParseError(uuid::Error), +} + +impl Display for ClientIdFileError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use ClientIdFileError::*; + match self { + NotFound => write!(f, "File not found"), + PermissionDenied => write!( + f, + "The operation lacked the necessary privileges to complete." + ), + IoError(e) => write!(f, "IO error occured: {e}"), + ParseError(e) => write!(f, "Parse error occured: {e}"), + } + } +} + +impl From<io::Error> for ClientIdFileError { + fn from(error: io::Error) -> Self { + match error.kind() { + io::ErrorKind::NotFound => ClientIdFileError::NotFound, + io::ErrorKind::PermissionDenied => ClientIdFileError::PermissionDenied, + _ => ClientIdFileError::IoError(error), + } + } +} + +impl From<uuid::Error> for ClientIdFileError { + fn from(error: uuid::Error) -> Self { + ClientIdFileError::ParseError(error) + } +} diff --git a/third_party/rust/glean-core/src/fd_logger.rs b/third_party/rust/glean-core/src/fd_logger.rs @@ -23,7 +23,7 @@ use serde::Serialize; /// side) to filter the log messages based on their level. /// The JSON payload of each message in an object with the following keys: /// - `level` (string): One of the logging levels defined here: -/// https://docs.rs/log/0.4.11/log/enum.Level.html +/// <https://docs.rs/log/0.4.11/log/enum.Level.html> /// - `message` (string): The logging message. pub struct FdLogger { pub file: RwLock<File>, diff --git a/third_party/rust/glean-core/src/histogram/exponential.rs b/third_party/rust/glean-core/src/histogram/exponential.rs @@ -80,7 +80,7 @@ impl Bucketing for PrecomputedExponential { /// Get the bucket for the sample. /// /// This uses a binary search to locate the index `i` of the bucket such that: - /// bucket[i] <= sample < bucket[i+1] + /// `bucket[i] <= sample < bucket[i+1]` fn sample_to_bucket_minimum(&self, sample: u64) -> u64 { let limit = match self.ranges().binary_search(&sample) { // Found an exact match to fit it in diff --git a/third_party/rust/glean-core/src/histogram/linear.rs b/third_party/rust/glean-core/src/histogram/linear.rs @@ -58,7 +58,7 @@ impl Bucketing for PrecomputedLinear { /// Get the bucket for the sample. /// /// This uses a binary search to locate the index `i` of the bucket such that: - /// bucket[i] <= sample < bucket[i+1] + /// `bucket[i] <= sample < bucket[i+1]` fn sample_to_bucket_minimum(&self, sample: u64) -> u64 { let limit = match self.ranges().binary_search(&sample) { // Found an exact match to fit it in diff --git a/third_party/rust/glean-core/src/internal_metrics.rs b/third_party/rust/glean-core/src/internal_metrics.rs @@ -388,12 +388,44 @@ impl DatabaseMetrics { } } +/// Possible values for the `glean.health.exception_state` health metric. +pub enum ExceptionState { + /// No database on disk, but the plaintext file contained a valid client ID. + EmptyDb, + /// Existing database, but no client ID, however a client ID in the plaintext file. + RegenDb, + /// The database contained a c0ffee client ID. + C0ffeeInDb, + /// The client IDs in the database and the plaintext file differ. + ClientIdMismatch, +} + +impl From<ExceptionState> for String { + fn from(value: ExceptionState) -> Self { + use ExceptionState::*; + String::from(match value { + EmptyDb => "empty-db", + RegenDb => "regen-db", + C0ffeeInDb => "c0ffee-in-db", + ClientIdMismatch => "client-id-mismatch", + }) + } +} + #[derive(Debug, MallocSizeOf)] pub struct HealthMetrics { // Information about the data directory prior to Glean initialization. pub data_directory_info: ObjectMetric, // A running count of the number of initializations. pub init_count: CounterMetric, + + // An exceptional state was detected upon trying to laod the database. + pub exception_state: StringMetric, + // A client_id recovered from a `client_id.txt` file on disk. + pub recovered_client_id: UuidMetric, + + pub file_read_error: LabeledCounter, + pub file_write_error: LabeledCounter, } impl HealthMetrics { @@ -415,6 +447,57 @@ impl HealthMetrics { disabled: false, dynamic_label: None, }), + exception_state: StringMetric::new(CommonMetricData { + name: "exception_state".into(), + category: "glean.health".into(), + send_in_pings: vec!["health".into()], + lifetime: Lifetime::Ping, + disabled: false, + dynamic_label: None, + }), + recovered_client_id: UuidMetric::new(CommonMetricData { + name: "recovered_client_id".into(), + category: "glean.health".into(), + send_in_pings: vec!["health".into()], + lifetime: Lifetime::Ping, + disabled: false, + dynamic_label: None, + }), + file_read_error: LabeledMetric::<CounterMetric>::new( + LabeledMetricData::Common { + cmd: CommonMetricData { + category: "glean.health".into(), + name: "file_read_error".into(), + send_in_pings: vec!["health".into()], + lifetime: Lifetime::Ping, + disabled: false, + dynamic_label: None, + }, + }, + Some(vec![ + Cow::from("parse"), + Cow::from("permission-denied"), + Cow::from("io"), + Cow::from("c0ffee-in-file"), + ]), + ), + file_write_error: LabeledMetric::<CounterMetric>::new( + LabeledMetricData::Common { + cmd: CommonMetricData { + category: "glean.health".into(), + name: "file_write_error".into(), + send_in_pings: vec!["health".into()], + lifetime: Lifetime::Ping, + disabled: false, + dynamic_label: None, + }, + }, + Some(vec![ + Cow::from("not-found"), + Cow::from("permission-denied"), + Cow::from("io"), + ]), + ), } } } diff --git a/third_party/rust/glean-core/src/lib.rs b/third_party/rust/glean-core/src/lib.rs @@ -587,6 +587,11 @@ fn initialize_inner( // Now capture a post_init snapshot of the state of Glean's data directories after initialization to send // in a health ping with reason "post_init". record_dir_info_and_submit_health_ping(collect_directory_info(data_path), "post_init"); + + let state = global_state().lock().unwrap(); + if let Err(e) = state.callbacks.trigger_upload() { + log::error!("Triggering upload failed. Error: {}", e); + } } let state = global_state().lock().unwrap(); state.callbacks.initialize_finished(); @@ -802,7 +807,7 @@ fn initialize_core_metrics(glean: &Glean, client_info: &ClientInfoMetrics) { } } -/// Checks if [`initialize`] was ever called. +/// Checks if [`glean_initialize`] was ever called. /// /// # Returns /// @@ -935,7 +940,7 @@ pub fn set_ping_enabled(ping: &PingType, enabled: bool) { } } -/// Register a new [`PingType`](PingType). +/// Register a new [`PingType`]. pub(crate) fn register_ping_type(ping: &PingType) { // If this happens after Glean.initialize is called (and returns), // we dispatch ping registration on the thread pool. diff --git a/third_party/rust/glean-core/src/metrics/custom_distribution.rs b/third_party/rust/glean-core/src/metrics/custom_distribution.rs @@ -123,7 +123,7 @@ impl CustomDistributionMetric { /// ## Notes /// /// Discards any negative value of `sample` and reports an - /// [`ErrorType::InvalidValue`](crate::ErrorType::InvalidValue). + /// [`ErrorType::InvalidValue`]. pub fn accumulate_single_sample(&self, sample: i64) { let metric = self.clone(); crate::launch_with_glean(move |glean| metric.accumulate_samples_sync(glean, &[sample])) diff --git a/third_party/rust/glean-core/src/metrics/dual_labeled_counter.rs b/third_party/rust/glean-core/src/metrics/dual_labeled_counter.rs @@ -275,7 +275,7 @@ pub fn separate_label_into_key_and_category(label: &str) -> Option<(&str, &str)> } /// Construct and return a label from a given key and category with the RECORD_SEPARATOR -/// characters in the format: <RS><key><RS><category> +/// characters in the format: `<RS><key><RS><category>` pub fn make_label_from_key_and_category(key: &str, category: &str) -> String { format!( "{}{}{}{}", diff --git a/third_party/rust/glean-core/src/ping/mod.rs b/third_party/rust/glean-core/src/ping/mod.rs @@ -231,7 +231,6 @@ impl PingMaker { /// /// A map of header names to header values. /// Might be empty if there are no extra headers to send. - /// ``` fn get_headers(&self, glean: &Glean) -> HeaderMap { let mut headers_map = HeaderMap::new(); diff --git a/third_party/rust/glean-core/src/traits/custom_distribution.rs b/third_party/rust/glean-core/src/traits/custom_distribution.rs @@ -24,8 +24,7 @@ pub trait CustomDistribution: TestGetValue<Output = DistributionData> { /// ## Notes /// /// Discards any negative value in `samples` and report an - /// [`ErrorType::InvalidValue`](crate::ErrorType::InvalidValue) for each of - /// them. + /// [`ErrorType::InvalidValue`] for each of them. fn accumulate_samples_signed(&self, samples: Vec<i64>); /// Accumulates precisely one signed sample in the metric. @@ -41,7 +40,7 @@ pub trait CustomDistribution: TestGetValue<Output = DistributionData> { /// ## Notes /// /// Discards any negative value of `sample` and reports an - /// [`ErrorType::InvalidValue`](crate::ErrorType::InvalidValue). + /// [`ErrorType::InvalidValue`]. fn accumulate_single_sample_signed(&self, sample: i64); /// **Exported for test purposes.** diff --git a/third_party/rust/glean-core/src/upload/policy.rs b/third_party/rust/glean-core/src/upload/policy.rs @@ -26,8 +26,8 @@ pub struct Policy { /// /// Limiting this is necessary to avoid infinite loops on requesting upload tasks. max_recoverable_failures: Option<u32>, - /// The maximum of [`PingUploadTask::Wait`] responses a user may get in a row - /// when calling [`get_upload_task`]. + /// The maximum of [`PingUploadTask::Wait`](crate::PingUploadTask::Wait) responses a user may get in a row + /// when calling [`get_upload_task`](crate::Glean::get_upload_task). /// /// Limiting this is necessary to avoid infinite loops on requesting upload tasks. max_wait_attempts: Option<u32>, diff --git a/third_party/rust/glean-core/tests/clientid_textfile.rs b/third_party/rust/glean-core/tests/clientid_textfile.rs @@ -0,0 +1,534 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// 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/. + +mod common; +use crate::common::*; + +use std::fs; +use std::path::Path; + +use rkv::Rkv; +use rkv::StoreOptions; +use uuid::Uuid; + +use glean_core::metrics::*; +use glean_core::CommonMetricData; +use glean_core::Lifetime; + +fn clientid_metric() -> UuidMetric { + UuidMetric::new(CommonMetricData { + name: "client_id".into(), + category: "".into(), + send_in_pings: vec!["glean_client_info".into()], + lifetime: Lifetime::User, + disabled: false, + dynamic_label: None, + }) +} + +fn clientid_from_file(data_path: &Path) -> Option<Uuid> { + let path = data_path.join("client_id.txt"); + let uuid_str = fs::read_to_string(path).ok()?; + Uuid::parse_str(uuid_str.trim_end()).ok() +} + +fn write_clientid_to_file(data_path: &Path, uuid: &Uuid) -> Option<()> { + let mut buffer = Uuid::encode_buffer(); + let uuid_str = uuid.hyphenated().encode_lower(&mut buffer); + write_clientid_to_file_str(data_path, uuid_str) +} + +fn write_clientid_to_file_str(data_path: &Path, s: &str) -> Option<()> { + let path = data_path.join("client_id.txt"); + fs::write(path, s.as_bytes()).ok()?; + Some(()) +} + +#[test] +fn writes_clientid_file() { + let (glean, temp) = new_glean(None); + + let db_client_id = clientid_metric().get_value(&glean, None).unwrap(); + let file_client_id = clientid_from_file(temp.path()).unwrap(); + + assert_eq!(file_client_id, db_client_id); + + glean.submit_ping_by_name("health", Some("pre_init")); + let mut pending = get_queued_pings(temp.path()).unwrap(); + assert_eq!(1, pending.len()); + let payload = pending.pop().unwrap().1; + + let state = &payload["metrics"]["string"]["glean.health.exception_state"]; + assert_eq!(&serde_json::Value::Null, state); +} + +#[test] +fn reused_clientid_from_file() { + let temp = tempfile::tempdir().unwrap(); + let new_uuid = Uuid::new_v4(); + + write_clientid_to_file(temp.path(), &new_uuid).unwrap(); + + let (glean, temp) = new_glean(Some(temp)); + // TODO(bug 1996862): We don't run the mitigation yet + //let db_client_id = clientid_metric().get_value(&glean, None).unwrap(); + //assert_eq!(new_uuid, db_client_id); + + glean.submit_ping_by_name("health", Some("pre_init")); + let mut pending = get_queued_pings(temp.path()).unwrap(); + assert_eq!(1, pending.len()); + let payload = pending.pop().unwrap().1; + + let state = payload["metrics"]["string"]["glean.health.exception_state"].as_str(); + assert_eq!(Some("empty-db"), state); +} + +#[test] +fn restores_clientid_file_from_db() { + let (db_client_id, temp) = { + // Ensure we initialize once to get a client_id + let (glean, temp) = new_glean(None); + let db_client_id = clientid_metric().get_value(&glean, None).unwrap(); + drop(glean); + + (db_client_id, temp) + }; + + // Removing the file. `Glean::new` should restore it + fs::remove_file(temp.path().join("client_id.txt")).unwrap(); + + let (glean, temp) = new_glean(Some(temp)); + let file_client_id = clientid_from_file(temp.path()).unwrap(); + assert_eq!(file_client_id, db_client_id); + + let db_client_id2 = clientid_metric().get_value(&glean, None).unwrap(); + assert_eq!(db_client_id, db_client_id2); + + glean.submit_ping_by_name("health", Some("pre_init")); + let mut pending = get_queued_pings(temp.path()).unwrap(); + assert_eq!(1, pending.len()); + let payload = pending.pop().unwrap().1; + + let state = payload["metrics"]["string"]["glean.health.exception_state"].as_str(); + assert_eq!(None, state); +} + +#[test] +fn clientid_regen_issue_with_existing_db() { + let (file_client_id, temp) = { + // Ensure we initialize once to get a client_id + let (glean, temp) = new_glean(None); + let file_client_id = clientid_from_file(temp.path()).unwrap(); + drop(glean); + + (file_client_id, temp) + }; + + // We modify the database and ONLY clear out the client id. + { + let path = temp.path().join("db"); + let rkv = Rkv::new::<rkv::backend::SafeMode>(&path).unwrap(); + let user_store = rkv.open_single("user", StoreOptions::create()).unwrap(); + + // We know this. + let client_id_key = "glean_client_info#client_id"; + + let mut writer = rkv.write().unwrap(); + user_store.delete(&mut writer, client_id_key).unwrap(); + writer.commit().unwrap(); + } + + let (glean, temp) = new_glean(Some(temp)); + + // TODO(bug 1996862): We don't run the mitigation yet + _ = file_client_id; + //let db_client_id = clientid_metric().get_value(&glean, None).unwrap(); + //assert_eq!(file_client_id, db_client_id); + + glean.submit_ping_by_name("health", Some("pre_init")); + let mut pending = get_queued_pings(temp.path()).unwrap(); + assert_eq!(1, pending.len()); + let payload = pending.pop().unwrap().1; + + let state = payload["metrics"]["string"]["glean.health.exception_state"].as_str(); + assert_eq!(Some("regen-db"), state); +} + +#[test] +fn db_client_id_prefered_over_file_client_id() { + let (db_client_id, temp) = { + // Ensure we initialize once to get a client_id + let (glean, temp) = new_glean(None); + let db_client_id = clientid_metric().get_value(&glean, None).unwrap(); + drop(glean); + + (db_client_id, temp) + }; + + // We modify the client id file + let new_uuid = Uuid::new_v4(); + write_clientid_to_file(temp.path(), &new_uuid).unwrap(); + + let (glean, temp) = new_glean(Some(temp)); + let db_client_id2 = clientid_metric().get_value(&glean, None).unwrap(); + assert_eq!(db_client_id, db_client_id2); + + let file_client_id = clientid_from_file(temp.path()).unwrap(); + assert_eq!(file_client_id, db_client_id); + + glean.submit_ping_by_name("health", Some("pre_init")); + let mut pending = get_queued_pings(temp.path()).unwrap(); + assert_eq!(1, pending.len()); + let payload = pending.pop().unwrap().1; + + let state = payload["metrics"]["string"]["glean.health.exception_state"].as_str(); + assert_eq!(Some("client-id-mismatch"), state); + + let exception_uuid = &payload["metrics"]["uuid"]["glean.health.recovered_client_id"].as_str(); + let mut buffer = Uuid::encode_buffer(); + let expected_uuid = &*new_uuid.hyphenated().encode_lower(&mut buffer); + assert_eq!(&Some(expected_uuid), exception_uuid); +} + +#[test] +fn c0ffee_in_db_gets_overwritten_by_stored_client_id() { + let (file_client_id, temp) = { + // Ensure we initialize once to get a client_id + let (glean, temp) = new_glean(None); + let file_client_id = clientid_from_file(temp.path()).unwrap(); + drop(glean); + + (file_client_id, temp) + }; + + // We modify the database and ONLY set the client id to c0ffee. + { + let path = temp.path().join("db"); + let rkv = Rkv::new::<rkv::backend::SafeMode>(&path).unwrap(); + let user_store = rkv.open_single("user", StoreOptions::create()).unwrap(); + + // We know this. + let client_id_key = "glean_client_info#client_id"; + + let mut writer = rkv.write().unwrap(); + let encoded = bincode::serialize(&Metric::Uuid(String::from( + "c0ffeec0-ffee-c0ff-eec0-ffeec0ffeec0", + ))) + .unwrap(); + let known_client_id = rkv::Value::Blob(&encoded); + user_store + .put(&mut writer, client_id_key, &known_client_id) + .unwrap(); + writer.commit().unwrap(); + } + + let (glean, temp) = new_glean(Some(temp)); + + // TODO(bug 1996862): We don't run the mitigation yet + _ = file_client_id; + //let db_client_id = clientid_metric().get_value(&glean, None).unwrap(); + //assert_eq!(file_client_id, db_client_id); + + glean.submit_ping_by_name("health", Some("pre_init")); + let mut pending = get_queued_pings(temp.path()).unwrap(); + assert_eq!(1, pending.len()); + let payload = pending.pop().unwrap().1; + + let state = payload["metrics"]["string"]["glean.health.exception_state"].as_str(); + assert_eq!(Some("c0ffee-in-db"), state); + + let mut buffer = Uuid::encode_buffer(); + let file_client_id = &*file_client_id.hyphenated().encode_lower(&mut buffer); + + let exception_uuid = &payload["metrics"]["uuid"]["glean.health.recovered_client_id"].as_str(); + assert_eq!(&Some(file_client_id), exception_uuid); + + // TODO(bug 1996862): We don't run the mitigation yet + //let exception_uuid = &payload["client_info"]["client_id"].as_str(); + //assert_eq!(&Some(file_client_id), exception_uuid); + // instead we ensure we don't see the c0ffee ID either. + let client_id = payload["client_info"]["client_id"].as_str().unwrap(); + assert_ne!("c0ffeec0-ffee-c0ff-eec0-ffeec0ffeec0", client_id); +} + +#[test] +fn clientid_file_gets_deleted() { + let (mut glean, temp) = new_glean(None); + + let path = temp.path().join("client_id.txt"); + assert!(path.exists()); + + let file_client_id = clientid_from_file(temp.path()); + assert!(file_client_id.is_some()); + + glean.set_upload_enabled(false); + assert!(!path.exists()); +} + +#[test] +fn clientid_file_casing_doesnt_matter() { + let (file_client_id, temp) = { + // Ensure we initialize once to get a client_id + let (glean, temp) = new_glean(None); + let file_client_id = clientid_from_file(temp.path()).unwrap(); + drop(glean); + + (file_client_id, temp) + }; + + { + let mut buffer = Uuid::encode_buffer(); + let uuid_str = file_client_id.hyphenated().encode_lower(&mut buffer); + write_clientid_to_file_str(temp.path(), &uuid_str.to_uppercase()); + } + + let (glean, temp) = new_glean(Some(temp)); + + let db_client_id = clientid_metric().get_value(&glean, None).unwrap(); + assert_eq!(file_client_id, db_client_id); + + glean.submit_ping_by_name("health", Some("pre_init")); + let mut pending = get_queued_pings(temp.path()).unwrap(); + assert_eq!(1, pending.len()); + let payload = pending.pop().unwrap().1; + + let state = &payload["metrics"]["string"]["glean.health.exception_state"]; + assert_eq!(&serde_json::Value::Null, state); +} + +#[test] +fn invalid_client_id_file_doesnt_crash() { + let (glean, mut temp) = new_glean(None); + drop(glean); + + let test_values = &[ + "", + "abc", + "e9f87afa-48b6-489e-841a-401522aaf1f", // one byte short + "e9f87afa-48b6-489e-841a-401522aaf1ff\n", // explicit newline! + "k9f87afa-48b6-489e-841a-401522aaf1ff", // k is not a valid char + "\0\0\0", + ]; + + for value in test_values { + write_clientid_to_file_str(temp.path(), value); + + let (glean, new_temp) = new_glean(Some(temp)); + drop(glean); + temp = new_temp; + } + + // Now testing with a directory in place. + { + let p = temp.path().join("client_id.txt"); + fs::remove_file(&p).unwrap(); + fs::create_dir_all(&p).unwrap(); + + let (glean, new_temp) = new_glean(Some(temp)); + drop(glean); + temp = new_temp; + } + + drop(temp); +} + +mod read_errors { + use super::*; + + #[test] + fn parse_error_is_reported() { + let (glean, temp) = new_glean(None); + drop(glean); + + write_clientid_to_file_str(temp.path(), "abc"); + + let (glean, temp) = new_glean(Some(temp)); + + glean.submit_ping_by_name("health", Some("pre_init")); + let mut pending = get_queued_pings(temp.path()).unwrap(); + assert_eq!(1, pending.len()); + let payload = pending.pop().unwrap().1; + + let state = &payload["metrics"]["string"]["glean.health.exception_state"]; + assert_eq!(&serde_json::Value::Null, state); + + let state = payload["metrics"]["labeled_counter"]["glean.health.file_read_error"]["parse"] + .as_i64() + .unwrap(); + assert_eq!(1, state); + } + + #[test] + fn io_error_is_reported() { + let (glean, temp) = new_glean(None); + drop(glean); + + let p = temp.path().join("client_id.txt"); + fs::remove_file(&p).unwrap(); + fs::create_dir_all(&p).unwrap(); + + let (glean, temp) = new_glean(Some(temp)); + + glean.submit_ping_by_name("health", Some("pre_init")); + let mut pending = get_queued_pings(temp.path()).unwrap(); + assert_eq!(1, pending.len()); + let payload = pending.pop().unwrap().1; + + let state = &payload["metrics"]["string"]["glean.health.exception_state"]; + assert_eq!(&serde_json::Value::Null, state); + + let state = payload["metrics"]["labeled_counter"]["glean.health.file_read_error"]["io"] + .as_i64() + .unwrap(); + assert_eq!(1, state); + } + + #[cfg(unix)] + #[test] + fn permission_error_is_reported() { + use std::os::unix::fs::PermissionsExt; + + let (glean, temp) = new_glean(None); + drop(glean); + + let p = temp.path().join("client_id.txt"); + // Remove write permissions on the file. + let attr = fs::metadata(&p).unwrap(); + let original_permissions = attr.permissions(); + let mut permissions = original_permissions.clone(); + // No permissions at all, no read, no write, for anyone. + permissions.set_mode(0o0); + fs::set_permissions(&p, permissions).unwrap(); + + let (glean, temp) = new_glean(Some(temp)); + + glean.submit_ping_by_name("health", Some("pre_init")); + let mut pending = get_queued_pings(temp.path()).unwrap(); + assert_eq!(1, pending.len()); + let payload = pending.pop().unwrap().1; + + let state = &payload["metrics"]["string"]["glean.health.exception_state"]; + assert_eq!(&serde_json::Value::Null, state); + + let state = payload["metrics"]["labeled_counter"]["glean.health.file_read_error"] + ["permission-denied"] + .as_i64() + .unwrap(); + assert_eq!(1, state); + } + + #[test] + fn c0ffee_is_reported() { + let (glean, temp) = new_glean(None); + drop(glean); + + write_clientid_to_file_str(temp.path(), "c0ffeec0-ffee-c0ff-eec0-ffeec0ffeec0"); + + let (glean, temp) = new_glean(Some(temp)); + + glean.submit_ping_by_name("health", Some("pre_init")); + let mut pending = get_queued_pings(temp.path()).unwrap(); + assert_eq!(1, pending.len()); + let payload = pending.pop().unwrap().1; + + let state = &payload["metrics"]["string"]["glean.health.exception_state"]; + assert_eq!(&serde_json::Value::Null, state); + + let state = payload["metrics"]["labeled_counter"]["glean.health.file_read_error"] + ["c0ffee-in-file"] + .as_i64() + .unwrap(); + assert_eq!(1, state); + } +} + +mod write_errors { + use super::*; + + #[cfg(unix)] + #[test] + fn permission_error_is_reported() { + let (glean, temp) = new_glean(None); + drop(glean); + + // Force empty file, so that it will be written again later. + write_clientid_to_file_str(temp.path(), ""); + + let p = temp.path().join("client_id.txt"); + // Remove write permissions on the file. + let attr = fs::metadata(&p).unwrap(); + let original_permissions = attr.permissions(); + let mut permissions = original_permissions.clone(); + // No permissions at all, no read, no write, for anyone. + permissions.set_readonly(true); + fs::set_permissions(&p, permissions).unwrap(); + + let (glean, temp) = new_glean(Some(temp)); + + glean.submit_ping_by_name("health", Some("pre_init")); + let mut pending = get_queued_pings(temp.path()).unwrap(); + assert_eq!(1, pending.len()); + let payload = pending.pop().unwrap().1; + + let state = &payload["metrics"]["string"]["glean.health.exception_state"]; + assert_eq!(&serde_json::Value::Null, state); + + let state = payload["metrics"]["labeled_counter"]["glean.health.file_read_error"]["parse"] + .as_i64() + .unwrap(); + assert_eq!(1, state); + + let state = payload["metrics"]["labeled_counter"]["glean.health.file_write_error"] + ["permission-denied"] + .as_i64() + .unwrap(); + assert_eq!(1, state); + } + + #[cfg(unix)] + #[test] + fn permission_error_on_later_regeneration() { + let (mut glean, temp) = new_glean(None); + + glean.submit_ping_by_name("health", Some("pre_init")); + let pending = get_queued_pings(temp.path()).unwrap(); + assert_eq!(1, pending.len()); + + glean.set_upload_enabled(false); + + let path = temp.path().join("client_id.txt"); + assert!(!path.exists()); + + // Force empty file, so that it will be written again later. + write_clientid_to_file_str(temp.path(), ""); + + let p = temp.path().join("client_id.txt"); + // Remove write permissions on the file. + let attr = fs::metadata(&p).unwrap(); + let original_permissions = attr.permissions(); + let mut permissions = original_permissions.clone(); + // No permissions at all, no read, no write, for anyone. + permissions.set_readonly(true); + fs::set_permissions(&p, permissions).unwrap(); + + glean.set_upload_enabled(true); + + glean.submit_ping_by_name("health", Some("pre_init")); + let mut pending = get_queued_pings(temp.path()).unwrap(); + assert_eq!(1, pending.len()); + let payload = pending.pop().unwrap().1; + + let state = &payload["metrics"]["string"]["glean.health.exception_state"]; + assert_eq!(&serde_json::Value::Null, state); + + let state = &payload["metrics"]["labeled_counter"]["glean.health.file_read_error"]["parse"]; + assert_eq!(&serde_json::Value::Null, state); + + let state = payload["metrics"]["labeled_counter"]["glean.health.file_write_error"] + ["permission-denied"] + .as_i64() + .unwrap(); + assert_eq!(1, state); + } +} diff --git a/third_party/rust/glean/.cargo-checksum.json b/third_party/rust/glean/.cargo-checksum.json @@ -1 +1 @@ -{"files":{"Cargo.lock":"975185d5c453db2d68602487bb2ff0267798edcc805181d61799feafe2bcd3ab","Cargo.toml":"db140ddeda034ae3abc7b8668055daa189166c21b1e5abb31e9870fada0fab82","LICENSE":"1f256ecad192880510e84ad60474eab7589218784b9a50bc7ceee34c2b91f1d5","README.md":"5627cc81e6187ab6c2b4dff061af16d559edcab64ba786bac39daa69c703c595","src/common_test.rs":"c86cccfb7da1506cfed29cb2ee13d839b7ac7cffdfd70793c9665bb44e0b684f","src/configuration.rs":"de65ab99a26b4547be20803bc195cb50a6ab40b1a3f49a2e6230fed5a9d7a8d8","src/core_metrics.rs":"fef8fb4e5fa57c179836c6eb2cf59278fe3b8b036dbe57b0ff02971b4acd822f","src/lib.rs":"3c73125967a4cb026bd4d7ca898edd3d2bee61c35abcfbcd875852552c540b52","src/net/http_uploader.rs":"0a94ac3cd87cb021529dee46d537765ab8d923e0f4ac7615225e878d3739e6dc","src/net/mod.rs":"95d2c0744caa9e336f4c4d0cef698202eb65d39b4c025a2da3902dffecdede73","src/private/event.rs":"e6c76b384771b6ca5e0bc77f0e563ff768798be0985b1b7858e5921af497d197","src/private/mod.rs":"de67f272a20b9b5ead58a40073a82ba54f9dd1b7d0d14c7b916fc714c961ead4","src/private/object.rs":"4dde92c7700d68fb7113ae57ef84c0ec14f5c786a2f0758ae700f458891613be","src/private/ping.rs":"8ec514e88c1fcba13560522c83e39643edf7d74a3dc733e273984c46c781b2d2","src/system.rs":"d602804a72258bfd65e51c571946631732ee27d81342d8aa406e47fdd241bbfa","src/test.rs":"ffc54a3cab5950148cee0750b11de883f1346bce1d9b7755143e3d5ba78f7533","tests/collection_enabled.rs":"3327a949dbdeec493d661261abda68ffa71acc50ab24cba4fde5302749e6f16b","tests/collection_enabled_bin.rs":"d3a6458b84012a447e5cb792f2292a06951ed252fad803b9166b437bacba542c","tests/common/mod.rs":"2fd391c5eb45f56fdfa3261dd631406c67ed36b10b0d5432febe2483da5c9d89","tests/custom_distribution_buffered.rs":"aa70bc2e2aca14c5c8a30dafd246d2657769fa7cdf4d09a6f10a03a356c696f6","tests/dual_labeled_counter_metric.rs":"91e9e206a4f331e067f55e5cde7eb0b893e41ea08eb125371e7b03fe44cb1445","tests/health_ping.rs":"1489995efa25fe1518003b048990fb405c90664e270802ec913cc8de737e4b74","tests/init_fails.rs":"ca7fa1b3dd6a21a9e005b7a4f0a18664c4bceb952dd463db8316500f72280d5b","tests/interruptible_shutdown.rs":"60f057bd495843a6d97cc9f875be1aa944b43c5f9a034c58d648b2d629195989","tests/labeled_metrics.rs":"d9b14025054a0ee9b89b60e720435828257eda7ec61f349389ab387923d3954c","tests/memory_distribution_buffered.rs":"e809a8de47ea4ac10c1181651d48b11aa11f57704d95f10ae194762e5d3d2982","tests/metric_metadata.rs":"e0188fcf8dc8f754f905a0662932d341646429589795bf10407645c0b3034ad2","tests/near-empty-c0ffee-db.safe.bin":"89afb3bb8fc94430fb0ed0fe55f85f3f8bcc8fd0fed69a9df13cc560294ec9f5","tests/never_init.rs":"51fff5618f6603bc0945d70131698d10a1c6275f43bbc22a2de5807f8a79229f","tests/no_time_to_init.rs":"2ede23df6618ff1cb5ae3b7bbf95900ad0fd92072afa2e0319bf147b4f75cefc","tests/overflowing_preinit.rs":"dc4d5eccb84c3c9ca25221bdc4a9bbf50ebc36d6d83d27f36d93f4562fb75d5d","tests/persist_ping_lifetime_nopanic.rs":"18379d3ffbf4a2c8c684c04ff7a0660b86dfbbb447db2d24dfed6073cb7ddf8f","tests/schema.rs":"23b49005402b914e55a0c5c155f30c2662c609f79be78d1385ec25b3600b3547","tests/signaling_done.rs":"d4ed662955ab665566508817d2d195405b44966667aabdee21510f5cb42dbe9f","tests/simple.rs":"79d78a4cdc439c92b4efbbb2aa181ec1fd232c7a6264ad7832ab2a574ea74939","tests/test-delayed-ping-data.sh":"b6a86e07e47c301529e3bcca5265eba091c6626e38a495ad05f94f1654e6450e","tests/test-enabled-pings.sh":"f094f5735c1bb66da854de0eae84098527fff9b59854888da6f2865308847383","tests/test-mps-delay.sh":"207661a72bf8c05a8b44ad40f208826338069e2f7f4dc3f589aabc35606732e8","tests/test-pending-gets-removed.sh":"7099aaf770cfae070ee4427557cb33981734e18fe145890917d5e8ca324ea011","tests/test-ping-lifetime-flush.sh":"ed7dfc57ce71677e6b310286aed563ab4baa69aad9554cbd045fe5e49903b63a","tests/test-shutdown-blocking.sh":"77402ba408ffb69c19da01d9bffc8a1be8c735efdb84575512f778a07e8e77d1","tests/test-thread-crashing.sh":"e0e5ca6caa95e43d78369eb227cb523108486b31f8289b97bc012c37cdf8bc5c","tests/timing_distribution_buffered.rs":"b96cd123ff830175597d32667477eac9102e05aea6726382c83b35b6bc66176c","tests/timing_distribution_single_sample.rs":"bca54132d5fec225788f0292e8b6c11fa658405dc0586a2bbd84b86c1558f4bd","tests/upload_timing.rs":"e7271e7a71fc5df98c9695a01f89039f4011849c709c8dc99af23bb7e047f3e1","tests/uploader_capabilities.rs":"0c2d70ed51fba444a94aef522e7cb9253c939551694babe0045b2358e3e85b34"},"package":"3acc348bbf540abeedf7c271daf952b1e24311e2ea1c1f8ac172a5cedf37ac91"} -\ No newline at end of file +{"files":{"Cargo.lock":"149eadad3b01fc04322a0fe288012ec975c53ccc316be3b1bbeecf39a8ae9f00","Cargo.toml":"6aa52417fd1c2c04a9d06e9716c6593a1b2146b17489cfd41bc52834de5ed365","LICENSE":"1f256ecad192880510e84ad60474eab7589218784b9a50bc7ceee34c2b91f1d5","README.md":"5627cc81e6187ab6c2b4dff061af16d559edcab64ba786bac39daa69c703c595","src/common_test.rs":"c86cccfb7da1506cfed29cb2ee13d839b7ac7cffdfd70793c9665bb44e0b684f","src/configuration.rs":"de65ab99a26b4547be20803bc195cb50a6ab40b1a3f49a2e6230fed5a9d7a8d8","src/core_metrics.rs":"fef8fb4e5fa57c179836c6eb2cf59278fe3b8b036dbe57b0ff02971b4acd822f","src/lib.rs":"73179ef356374879fd8ffbe9268f74a81a06de8fa3ba73dfe158b43be2fab249","src/net/http_uploader.rs":"0a94ac3cd87cb021529dee46d537765ab8d923e0f4ac7615225e878d3739e6dc","src/net/mod.rs":"9999cfcee8b3bf282ff753acb661893ae4c306c2cafd76029d630211b034e71e","src/private/event.rs":"e6c76b384771b6ca5e0bc77f0e563ff768798be0985b1b7858e5921af497d197","src/private/mod.rs":"de67f272a20b9b5ead58a40073a82ba54f9dd1b7d0d14c7b916fc714c961ead4","src/private/object.rs":"4dde92c7700d68fb7113ae57ef84c0ec14f5c786a2f0758ae700f458891613be","src/private/ping.rs":"8ec514e88c1fcba13560522c83e39643edf7d74a3dc733e273984c46c781b2d2","src/system.rs":"d602804a72258bfd65e51c571946631732ee27d81342d8aa406e47fdd241bbfa","src/test.rs":"ffc54a3cab5950148cee0750b11de883f1346bce1d9b7755143e3d5ba78f7533","tests/collection_enabled.rs":"3327a949dbdeec493d661261abda68ffa71acc50ab24cba4fde5302749e6f16b","tests/collection_enabled_bin.rs":"d3a6458b84012a447e5cb792f2292a06951ed252fad803b9166b437bacba542c","tests/common/mod.rs":"2fd391c5eb45f56fdfa3261dd631406c67ed36b10b0d5432febe2483da5c9d89","tests/custom_distribution_buffered.rs":"aa70bc2e2aca14c5c8a30dafd246d2657769fa7cdf4d09a6f10a03a356c696f6","tests/dual_labeled_counter_metric.rs":"91e9e206a4f331e067f55e5cde7eb0b893e41ea08eb125371e7b03fe44cb1445","tests/health_ping.rs":"d3da67d6b5b484b6f7985a5f4f26fffe1edab741001cfaf3406009fa41147a51","tests/health_ping_file_overwrite.rs":"f6997dce57475777694594d8c19f5931888de6dec9a616df625bf88a012df64e","tests/init_fails.rs":"ca7fa1b3dd6a21a9e005b7a4f0a18664c4bceb952dd463db8316500f72280d5b","tests/interruptible_shutdown.rs":"60f057bd495843a6d97cc9f875be1aa944b43c5f9a034c58d648b2d629195989","tests/labeled_metrics.rs":"d9b14025054a0ee9b89b60e720435828257eda7ec61f349389ab387923d3954c","tests/memory_distribution_buffered.rs":"e809a8de47ea4ac10c1181651d48b11aa11f57704d95f10ae194762e5d3d2982","tests/metric_metadata.rs":"e0188fcf8dc8f754f905a0662932d341646429589795bf10407645c0b3034ad2","tests/near-empty-c0ffee-db.safe.bin":"89afb3bb8fc94430fb0ed0fe55f85f3f8bcc8fd0fed69a9df13cc560294ec9f5","tests/never_init.rs":"51fff5618f6603bc0945d70131698d10a1c6275f43bbc22a2de5807f8a79229f","tests/no_time_to_init.rs":"2ede23df6618ff1cb5ae3b7bbf95900ad0fd92072afa2e0319bf147b4f75cefc","tests/overflowing_preinit.rs":"dc4d5eccb84c3c9ca25221bdc4a9bbf50ebc36d6d83d27f36d93f4562fb75d5d","tests/persist_ping_lifetime_nopanic.rs":"18379d3ffbf4a2c8c684c04ff7a0660b86dfbbb447db2d24dfed6073cb7ddf8f","tests/schema.rs":"23b49005402b914e55a0c5c155f30c2662c609f79be78d1385ec25b3600b3547","tests/signaling_done.rs":"a0c956aaf18e96912896df624e14a652c4276fc09da75d2e29a5f8c8ae99226e","tests/simple.rs":"79d78a4cdc439c92b4efbbb2aa181ec1fd232c7a6264ad7832ab2a574ea74939","tests/test-delayed-ping-data.sh":"b6a86e07e47c301529e3bcca5265eba091c6626e38a495ad05f94f1654e6450e","tests/test-enabled-pings.sh":"f094f5735c1bb66da854de0eae84098527fff9b59854888da6f2865308847383","tests/test-mps-delay.sh":"207661a72bf8c05a8b44ad40f208826338069e2f7f4dc3f589aabc35606732e8","tests/test-pending-gets-removed.sh":"7099aaf770cfae070ee4427557cb33981734e18fe145890917d5e8ca324ea011","tests/test-ping-lifetime-flush.sh":"ed7dfc57ce71677e6b310286aed563ab4baa69aad9554cbd045fe5e49903b63a","tests/test-shutdown-blocking.sh":"77402ba408ffb69c19da01d9bffc8a1be8c735efdb84575512f778a07e8e77d1","tests/test-thread-crashing.sh":"a76e07848a21bcf0e52612f56bc785297f3a8dec7c412c2b21c7485735140fda","tests/timing_distribution_buffered.rs":"b96cd123ff830175597d32667477eac9102e05aea6726382c83b35b6bc66176c","tests/timing_distribution_single_sample.rs":"bca54132d5fec225788f0292e8b6c11fa658405dc0586a2bbd84b86c1558f4bd","tests/upload_timing.rs":"e7271e7a71fc5df98c9695a01f89039f4011849c709c8dc99af23bb7e047f3e1","tests/uploader_capabilities.rs":"0c2d70ed51fba444a94aef522e7cb9253c939551694babe0045b2358e3e85b34"},"package":"48821b3640793b8922f8fcc1b2294f1ffd4eec8f5145c30efea229482360727f"} +\ No newline at end of file diff --git a/third_party/rust/glean/Cargo.lock b/third_party/rust/glean/Cargo.lock @@ -312,7 +312,7 @@ dependencies = [ [[package]] name = "glean" -version = "66.0.0" +version = "66.1.0" dependencies = [ "crossbeam-channel", "env_logger", @@ -331,9 +331,9 @@ dependencies = [ [[package]] name = "glean-core" -version = "66.0.0" +version = "66.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "540e40007a01e43b73904d1ee8f7efd4782f1a7f4a20ef1dd6d2c00fc18d54b6" +checksum = "6f2e01e857cf2ca46dfcaa1228289adec3ceefe59350e9194dc87edac4ce6eda" dependencies = [ "android_logger", "bincode", diff --git a/third_party/rust/glean/Cargo.toml b/third_party/rust/glean/Cargo.toml @@ -13,7 +13,7 @@ edition = "2021" rust-version = "1.82" name = "glean" -version = "66.0.0" +version = "66.1.0" authors = [ "Jan-Erik Rediger <jrediger@mozilla.com>", "The Glean Team <glean-team@mozilla.com>", @@ -64,6 +64,10 @@ name = "health_ping" path = "tests/health_ping.rs" [[test]] +name = "health_ping_file_overwrite" +path = "tests/health_ping_file_overwrite.rs" + +[[test]] name = "init_fails" path = "tests/init_fails.rs" @@ -131,7 +135,7 @@ path = "tests/uploader_capabilities.rs" version = "0.5" [dependencies.glean-core] -version = "66.0.0" +version = "66.1.0" [dependencies.inherent] version = "1" diff --git a/third_party/rust/glean/src/lib.rs b/third_party/rust/glean/src/lib.rs @@ -162,7 +162,7 @@ pub fn set_collection_enabled(enabled: bool) { /// Note that this needs to be public in order for RLB consumers to /// use Glean debugging facilities. /// -/// See [`glean_core::Glean.submit_ping_by_name`]. +/// See [`glean_core::Glean::submit_ping_by_name`]. pub fn submit_ping_by_name(ping: &str, reason: Option<&str>) { let ping = ping.to_string(); let reason = reason.map(|s| s.to_string()); diff --git a/third_party/rust/glean/src/net/mod.rs b/third_party/rust/glean/src/net/mod.rs @@ -110,7 +110,7 @@ impl UploadManager { /// Signals Glean to upload pings at the next best opportunity. pub(crate) fn trigger_upload(&self) { - // If no other upload proces is running, we're the one starting it. + // If no other upload process is running, we're the one starting it. // Need atomic compare/exchange to avoid any further races // or we can end up with 2+ uploader threads. if self @@ -124,6 +124,7 @@ impl UploadManager { ) .is_err() { + log::trace!("glean.upload thread running. Not starting another one."); return; } diff --git a/third_party/rust/glean/tests/health_ping.rs b/third_party/rust/glean/tests/health_ping.rs @@ -99,52 +99,48 @@ fn test_pre_post_init_health_pings_exist() { let preinits: Vec<_> = pings .iter() .filter(|(url, body, _)| { - url.contains("health") - && body.get("ping_info").unwrap().get("reason").unwrap() == "pre_init" + url.contains("health") && body["ping_info"]["reason"] == "pre_init" }) .collect(); assert_eq!(1, preinits.len()); + + let health_pings: Vec<_> = pings + .iter() + .filter(|(url, _, _)| url.contains("health")) + .collect(); assert_eq!( 1, - pings + health_pings .iter() - .filter(|(url, body, _)| url.contains("health") - && body.get("ping_info").unwrap().get("reason").unwrap() == "post_init") + .filter(|(_, body, _)| body["ping_info"]["reason"] == "post_init") .count() ); // Ensure both "health" pings have the same init count. assert_eq!( 2, - pings + health_pings .iter() - .filter(|(url, body, _)| url.contains("health") - && body - .get("metrics") - .unwrap() - .get("counter") - .unwrap() - .get("glean.health.init_count") - .unwrap() - == 1) + .filter(|(_, body, _)| body["metrics"]["counter"]["glean.health.init_count"] == 1) .count() ); + + let exception_state = &preinits[0].1["metrics"]["string"]["glean.health_exception_state"]; + assert_eq!(&JsonValue::Null, exception_state); + let exception_uuid = &preinits[0].1["metrics"]["uuid"]["glean.health_recovered_client_id"]; + assert_eq!(&JsonValue::Null, exception_uuid); + // An initial preinit "health" ping will show no db file sizes - let load_sizes = preinits[0] - .1 - .get("metrics") - .unwrap() - .get("object") - .unwrap() - .get("glean.database.load_sizes") + let load_sizes = preinits[0].1["metrics"]["object"]["glean.database.load_sizes"] + .as_object() .unwrap(); assert_eq!(None, load_sizes.get("new")); assert_eq!(None, load_sizes.get("open")); assert_eq!(None, load_sizes.get("post_open")); assert_eq!(None, load_sizes.get("post_open_user")); assert_eq!(None, load_sizes.get("post_load_ping_lifetime_data")); - assert_eq!(0, *load_sizes.get("user_records").unwrap()); - assert_eq!(0, *load_sizes.get("ping_records").unwrap()); - assert_eq!(0, *load_sizes.get("application_records").unwrap()); + assert_eq!(0, load_sizes["user_records"]); + assert_eq!(0, load_sizes["ping_records"]); + assert_eq!(0, load_sizes["application_records"]); assert_eq!(None, load_sizes.get("ping_memory_records")); assert_eq!(None, load_sizes.get("error")); @@ -161,53 +157,29 @@ fn test_pre_post_init_health_pings_exist() { .iter() .filter(|(url, body, _)| { url.contains("health") - && body.get("ping_info").unwrap().get("reason").unwrap() == "pre_init" - && body.get("ping_info").unwrap().get("seq").unwrap() == 2 + && body["ping_info"]["reason"] == "pre_init" + && body["ping_info"]["seq"] == 2 }) .collect(); // We should have a second "pre_init"-reason "health" ping now. assert_eq!(1, second_preinit.len()); - let load_sizes = second_preinit[0] - .1 - .get("metrics") - .unwrap() - .get("object") - .unwrap() - .get("glean.database.load_sizes") + let load_sizes = second_preinit[0].1["metrics"]["object"]["glean.database.load_sizes"] + .as_object() .unwrap(); - assert_ne!(0, load_sizes.get("new").unwrap().as_i64().unwrap()); - assert_ne!(0, load_sizes.get("open").unwrap().as_i64().unwrap()); - assert_ne!(0, load_sizes.get("post_open").unwrap().as_i64().unwrap()); - assert_ne!( - 0, - load_sizes.get("post_open_user").unwrap().as_i64().unwrap() - ); - assert_ne!( - 0, - load_sizes - .get("post_load_ping_lifetime_data") - .unwrap() - .as_i64() - .unwrap() - ); + assert_ne!(0, load_sizes["new"]); + assert_ne!(0, load_sizes["open"]); + assert_ne!(0, load_sizes["post_open"]); + assert_ne!(0, load_sizes["post_open_user"].as_i64().unwrap()); + assert_ne!(0, load_sizes["post_load_ping_lifetime_data"]); assert_eq!( - load_sizes.get("new").unwrap().as_i64(), - load_sizes - .get("post_load_ping_lifetime_data") - .unwrap() - .as_i64() - ); - assert!(0 < load_sizes.get("user_records").unwrap().as_i64().unwrap()); - assert!(0 < load_sizes.get("ping_records").unwrap().as_i64().unwrap()); - assert!( - 0 < load_sizes - .get("application_records") - .unwrap() - .as_i64() - .unwrap() + load_sizes["new"], + load_sizes["post_load_ping_lifetime_data"] ); + assert!(0 < load_sizes["user_records"].as_i64().unwrap()); + assert!(0 < load_sizes["ping_records"].as_i64().unwrap()); + assert!(0 < load_sizes["application_records"].as_i64().unwrap()); assert_eq!(None, load_sizes.get("ping_memory_records")); assert_eq!(None, load_sizes.get("error")); } diff --git a/third_party/rust/glean/tests/health_ping_file_overwrite.rs b/third_party/rust/glean/tests/health_ping_file_overwrite.rs @@ -0,0 +1,85 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// 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/. + +//! This integration test should model how the RLB is used when embedded in another Rust application +//! (e.g. FOG/Firefox Desktop). +//! +//! We write a single test scenario per file to avoid any state keeping across runs +//! (different files run as different processes). + +mod common; + +use std::{fs, io::Read}; + +use crossbeam_channel::{bounded, Sender}; +use flate2::read::GzDecoder; +use glean::{net, ConfigurationBuilder}; +use serde_json::Value as JsonValue; + +// Define a fake uploader that reports when and what it uploads. +#[derive(Debug)] +struct ReportingUploader { + sender: Sender<JsonValue>, +} + +impl net::PingUploader for ReportingUploader { + fn upload(&self, upload_request: net::CapablePingUploadRequest) -> net::UploadResult { + let upload_request = upload_request.capable(|_| true).unwrap(); + let body = upload_request.body; + let decode = |body: Vec<u8>| { + let mut gzip_decoder = GzDecoder::new(&body[..]); + let mut s = String::with_capacity(body.len()); + + gzip_decoder + .read_to_string(&mut s) + .ok() + .map(|_| &s[..]) + .or_else(|| std::str::from_utf8(&body).ok()) + .and_then(|payload| serde_json::from_str(payload).ok()) + .unwrap() + }; + + self.sender.send(decode(body)).unwrap(); + net::UploadResult::http_status(200) + } +} + +/// Test scenario: Write a client ID to the backup file and check that it's used after initialization. +#[test] +fn test_pre_post_init_health_pings_exist() { + common::enable_test_logging(); + + // Create a custom configuration to use a validating uploader. + let dir = tempfile::tempdir().unwrap(); + let tmpname = dir.path().to_path_buf(); + + let client_id = "e03cc2de-bc8b-4f9c-862f-b474d910899e"; + + // We write a random but fixed client ID, without there being a Glean database. + let clientid_txt = tmpname.join("client_id.txt"); + fs::write(&clientid_txt, client_id.as_bytes()).unwrap(); + + let (tx, rx) = bounded(1); + let cfg = ConfigurationBuilder::new(true, tmpname.clone(), "health-ping-test") + .with_server_endpoint("invalid-test-host") + .with_use_core_mps(false) + .with_uploader(ReportingUploader { sender: tx }) + .build(); + common::initialize(cfg); + + glean_core::glean_test_destroy_glean(false, Some(tmpname.display().to_string())); + + // Check for the initialization pings. + // Wait for the ping to arrive. + let payload = rx.recv().unwrap(); + + let exception_state = &payload["metrics"]["string"]["glean.health.exception_state"]; + assert_eq!("empty-db", exception_state); + let exception_uuid = &payload["metrics"]["uuid"]["glean.health_recovered_client_id"]; + assert_eq!(&JsonValue::Null, exception_uuid); + + // TODO(bug 1996862): We don't run the mitigation yet. + //let ping_client_id = &payload["client_info"]["client_id"]; + //assert_eq!(client_id, ping_client_id); +} diff --git a/third_party/rust/glean/tests/signaling_done.rs b/third_party/rust/glean/tests/signaling_done.rs @@ -85,6 +85,12 @@ fn signaling_done() { // Sync up with the upload thread. barrier.wait(); + // The uploader thread needs some CPU time to actually shut down. + // We yield and still wait to make the scheduler give it that time. + // Using just one of the ways wasn't reliable enough. + std::thread::yield_now(); + std::thread::sleep(std::time::Duration::from_millis(100)); + // Submit another ping and wait for it to do work. pings::custom_ping.submit(None); diff --git a/third_party/rust/glean/tests/test-thread-crashing.sh b/third_party/rust/glean/tests/test-thread-crashing.sh @@ -23,14 +23,19 @@ cargo run -p glean --example crashing-threads -- "$datapath" ret=$? count=$(ls -1q "$datapath/pending_pings" | wc -l) +# 2x "health", 1x "prototype" +expect_count=3 + # We expect 1 `prototype` ping: -if [[ $ret -eq 0 ]] && [[ "$count" -eq 3 ]]; then +if [[ $ret -eq 0 ]] && [[ "$count" -eq $expect_count ]]; then echo "test result: ok." exit 0 else echo "Assertions:" echo " ret - expected: 0, was: $ret" - echo " count - expected: 1, was: $count" + echo " count - expected: $expect_count, was: $count" + echo "The following pings are pending:" + head -1 $datapath/pending_pings/* || true echo "test result: FAILED." exit 101 fi