From 3a8fba376cf84cc10622576d123966a29898086c Mon Sep 17 00:00:00 2001 From: Jan-Erik Rediger Date: Fri, 31 May 2024 13:20:28 +0200 Subject: [PATCH 01/51] Backport v60.1.1 release notes --- .dictionary | 3 ++- CHANGELOG.md | 9 +++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.dictionary b/.dictionary index 1756263c42..1f070bd431 100644 --- a/.dictionary +++ b/.dictionary @@ -1,4 +1,4 @@ -personal_ws-1.1 en 279 utf-8 +personal_ws-1.1 en 282 utf-8 AAR AARs ABI @@ -9,6 +9,7 @@ AndroidX AppServices BEHAVIOUR BUGFIX +Backported Bool Booleans CLI diff --git a/CHANGELOG.md b/CHANGELOG.md index 3daa04ae80..13757be173 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,15 @@ * Rust * Accept a ping schedule map on initialize ([#2839](https://github.com/mozilla/glean/pull/2839)) +# v60.1.1 (2024-05-31) + +[Full changelog](https://github.com/mozilla/glean/compare/v60.1.0...v60.1.1) + +* Android + * Allow configuring `delayPingLifetimeIo` in Kotlin and auto-flush this data after 1000 writes. + It is also auto-flushed on background. ([#2851](https://github.com/mozilla/glean/pull/2851)) + (Backported changes) + # v60.1.0 (2024-05-06) [Full changelog](https://github.com/mozilla/glean/compare/v60.0.0...v60.1.0) From aa34cbd9fefb155a73e660a79ac1c31caaace953 Mon Sep 17 00:00:00 2001 From: Jan-Erik Rediger Date: Fri, 31 May 2024 13:40:03 +0200 Subject: [PATCH 02/51] Bumped version to 60.3.0 --- .buildconfig.yml | 2 +- CHANGELOG.md | 6 +++++- Cargo.lock | 4 ++-- DEPENDENCIES.md | 4 ++-- glean-core/Cargo.toml | 2 +- glean-core/rlb/Cargo.toml | 4 ++-- .../telemetry/glean-gradle-plugin/GleanGradlePlugin.groovy | 2 +- pyproject.toml | 2 +- 8 files changed, 15 insertions(+), 11 deletions(-) diff --git a/.buildconfig.yml b/.buildconfig.yml index 055ba31e8b..56ff8a8388 100644 --- a/.buildconfig.yml +++ b/.buildconfig.yml @@ -1,4 +1,4 @@ -libraryVersion: 60.2.0 +libraryVersion: 60.3.0 groupId: org.mozilla.telemetry projects: glean: diff --git a/CHANGELOG.md b/CHANGELOG.md index 13757be173..0909bd426c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Unreleased changes -[Full changelog](https://github.com/mozilla/glean/compare/v60.2.0...main) +[Full changelog](https://github.com/mozilla/glean/compare/v60.3.0...main) + +# v60.3.0 (2024-05-31) + +[Full changelog](https://github.com/mozilla/glean/compare/v60.2.0...v60.3.0) * Android * Allow configuring `delayPingLifetimeIo` in Kotlin and auto-flush this data after 1000 writes. diff --git a/Cargo.lock b/Cargo.lock index c0dc576708..fe2c3c3137 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -318,7 +318,7 @@ dependencies = [ [[package]] name = "glean" -version = "60.2.0" +version = "60.3.0" dependencies = [ "crossbeam-channel", "env_logger", @@ -359,7 +359,7 @@ dependencies = [ [[package]] name = "glean-core" -version = "60.2.0" +version = "60.3.0" dependencies = [ "android_logger", "bincode", diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md index 2cedbc0d92..d34ac131db 100644 --- a/DEPENDENCIES.md +++ b/DEPENDENCIES.md @@ -4951,9 +4951,9 @@ SOFTWARE. The following text applies to code linked from these dependencies: -* [glean-core 60.2.0]( https://github.com/mozilla/glean ) +* [glean-core 60.3.0]( https://github.com/mozilla/glean ) * [glean-build 14.0.1]( https://github.com/mozilla/glean ) -* [glean 60.2.0]( https://github.com/mozilla/glean ) +* [glean 60.3.0]( https://github.com/mozilla/glean ) * [zeitstempel 0.1.1]( https://github.com/badboy/zeitstempel ) ``` diff --git a/glean-core/Cargo.toml b/glean-core/Cargo.toml index 82a5e8243c..ffcf7faff3 100644 --- a/glean-core/Cargo.toml +++ b/glean-core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "glean-core" -version = "60.2.0" +version = "60.3.0" authors = ["Jan-Erik Rediger ", "The Glean Team "] description = "A modern Telemetry library" repository = "https://github.com/mozilla/glean" diff --git a/glean-core/rlb/Cargo.toml b/glean-core/rlb/Cargo.toml index 6c635c8203..c1d4ea9c1f 100644 --- a/glean-core/rlb/Cargo.toml +++ b/glean-core/rlb/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "glean" -version = "60.2.0" +version = "60.3.0" authors = ["Jan-Erik Rediger ", "The Glean Team "] description = "Glean SDK Rust language bindings" repository = "https://github.com/mozilla/glean" @@ -23,7 +23,7 @@ maintenance = { status = "actively-developed" } [dependencies.glean-core] path = ".." -version = "60.2.0" +version = "60.3.0" [dependencies] inherent = "1" diff --git a/gradle-plugin/src/main/groovy/mozilla/telemetry/glean-gradle-plugin/GleanGradlePlugin.groovy b/gradle-plugin/src/main/groovy/mozilla/telemetry/glean-gradle-plugin/GleanGradlePlugin.groovy index 28cb7735e3..6b19a2e680 100644 --- a/gradle-plugin/src/main/groovy/mozilla/telemetry/glean-gradle-plugin/GleanGradlePlugin.groovy +++ b/gradle-plugin/src/main/groovy/mozilla/telemetry/glean-gradle-plugin/GleanGradlePlugin.groovy @@ -552,7 +552,7 @@ except: void apply(Project project) { isOffline = project.gradle.startParameter.offline - project.ext.glean_version = "60.2.0" + project.ext.glean_version = "60.3.0" def parserVersion = gleanParserVersion(project) // Print the required glean_parser version to the console. This is diff --git a/pyproject.toml b/pyproject.toml index 976756e47f..0edcf4df7c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "glean-sdk" -version = "60.2.0" +version = "60.3.0" requires-python = ">=3.8" classifiers = [ "Intended Audience :: Developers", From d0ee5bfd1a5d7ba586693a020d00fb6cc5f8feb7 Mon Sep 17 00:00:00 2001 From: Jan-Erik Rediger Date: Fri, 31 May 2024 13:25:14 +0200 Subject: [PATCH 03/51] Escape html-like text in docs [doc only] --- glean-core/src/debug.rs | 6 +++--- glean-core/src/lib.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/glean-core/src/debug.rs b/glean-core/src/debug.rs index 88f807bd88..1fd02a2b7f 100644 --- a/glean-core/src/debug.rs +++ b/glean-core/src/debug.rs @@ -17,11 +17,11 @@ //! * **Debug tagging** - Adding the X-Debug-ID header to every ping request, //! allowing these tagged pings to be sent to the ["Ping Debug Viewer"](https://mozilla.github.io/glean/book/dev/core/internal/debug-pings.html). //! This may be set by calling glean.set_debug_view_tag(value: &str) -//! or by setting the environment variable GLEAN_DEBUG_VIEW_TAG=; +//! or by setting the environment variable `GLEAN_DEBUG_VIEW_TAG=`; //! * **Source tagging** - Adding the X-Source-Tags header to every ping request, //! allowing pings to be tagged with custom labels. -//! This may be set by calling glean.set_source_tags(value: Vec) -//! or by setting the environment variable GLEAN_SOURCE_TAGS=; +//! This may be set by calling `glean.set_source_tags(value: Vec)` +//! or by setting the environment variable `GLEAN_SOURCE_TAGS=`; //! //! Bindings may implement other debugging features, e.g. sending pings on demand. diff --git a/glean-core/src/lib.rs b/glean-core/src/lib.rs index 2649e59e75..f67ecd871f 100644 --- a/glean-core/src/lib.rs +++ b/glean-core/src/lib.rs @@ -310,7 +310,7 @@ pub trait OnGleanEvents: Send { } /// A callback handler that receives the base identifier of recorded events -/// The identifier is in the format: . +/// The identifier is in the format: `.` pub trait GleanEventListener: Send { /// Called when an event is recorded, indicating the id of the event fn on_event_recorded(&self, id: String); From c5210e81bd7f3a94b2f128bbe9299b51f5aadedb Mon Sep 17 00:00:00 2001 From: Jan-Erik Rediger Date: Fri, 31 May 2024 14:55:30 +0200 Subject: [PATCH 04/51] Backport v60.0.1 release notes [doc only] --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0909bd426c..254b8d6752 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,15 @@ * Python * Replace use of deprecated functionality (and make installs work on Python 3.12) ([#2820](https://github.com/mozilla/glean/pull/2820)) +# v60.0.1 (2024-05-31) + +[Full changelog](https://github.com/mozilla/glean/compare/v60.0.0...v60.0.1) + +* Android + * Allow configuring `delayPingLifetimeIo` in Kotlin and auto-flush this data after 1000 writes. + It is also auto-flushed on background. ([#2851](https://github.com/mozilla/glean/pull/2851)) + (Backported changes) + # v60.0.0 (2024-04-22) [Full changelog](https://github.com/mozilla/glean/compare/v59.0.0...v60.0.0) From b43d489801f3335b6dfc1009da577888cb5c6ef6 Mon Sep 17 00:00:00 2001 From: Jan-Erik Rediger Date: Mon, 3 Jun 2024 15:09:40 +0200 Subject: [PATCH 05/51] Remove experimental notation on `enableEventTimestamps` [doc only] --- .../java/mozilla/telemetry/glean/config/Configuration.kt | 2 +- glean-core/ios/Glean/Config/Configuration.swift | 2 +- glean-core/python/glean/config.py | 6 +++--- glean-core/rlb/src/configuration.rs | 4 ++-- glean-core/src/lib.rs | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/glean-core/android/src/main/java/mozilla/telemetry/glean/config/Configuration.kt b/glean-core/android/src/main/java/mozilla/telemetry/glean/config/Configuration.kt index 9a40bb7532..7450e0dc20 100644 --- a/glean-core/android/src/main/java/mozilla/telemetry/glean/config/Configuration.kt +++ b/glean-core/android/src/main/java/mozilla/telemetry/glean/config/Configuration.kt @@ -20,7 +20,7 @@ import mozilla.telemetry.glean.net.PingUploader * @property dataPath An optional [String] that specifies where to store data locally on the device. * This should ONLY be used when setting up Glean on a non-main process. * @property logLevel An optional [LevelFilter] that controls how verbose the internal logging is. - * @property enableEventTimestamps (Experimental) Whether to add a wallclock timestamp to all events. + * @property enableEventTimestamps Whether to add a wallclock timestamp to all events. * @property experimentationId An experimentation identifier derived by the application * to be sent with all pings. * @property enableInternalPings Whether to enable internal pings. diff --git a/glean-core/ios/Glean/Config/Configuration.swift b/glean-core/ios/Glean/Config/Configuration.swift index 47cc29024f..93f6828dbd 100644 --- a/glean-core/ios/Glean/Config/Configuration.swift +++ b/glean-core/ios/Glean/Config/Configuration.swift @@ -28,7 +28,7 @@ public struct Configuration { /// * dataPath an optional String that specifies where to store data locally on the device. /// This should ONLY be used when setting up Glean on a non-main process. /// * logLevel an optional log level that controls how verbose the internal logging is. - /// * enableEventTimestamps (Experimental) whether to add a wallclock timestamp to all events + /// * enableEventTimestamps whether to add a wallclock timestamp to all events /// * experimentationId An experimentation identifier derived by the application /// to be sent with all pings. /// * enableInternalPings Whether to enable internal pings. diff --git a/glean-core/python/glean/config.py b/glean-core/python/glean/config.py index 530a46dd15..b11fb8625e 100644 --- a/glean-core/python/glean/config.py +++ b/glean-core/python/glean/config.py @@ -48,8 +48,8 @@ def __init__( implementation. Defaults to `glean.net.HttpClientUploader`. allow_multiprocessing (bool): When True (default), use a subprocess to offload some work (such as ping uploading). - enable_event_timestamps (bool): (Experimental) Whether to add a - wallclock timestamp to all events. Default: `True`. + enable_event_timestamps (bool): Whether to add a wallclock timestamp + to all events. Default: `True`. experimentation_id (string): An experimentation identifier derived by the application to be sent with all pings. Default: None. enable_internal_pings (bool): Whether to enable internal pings. Default: `True`. @@ -98,7 +98,7 @@ def max_events(self) -> int: @property def enable_event_timestamps(self) -> bool: - """(Experimental) Whether to add a wallclock timestamp to all events.""" + """Whether to add a wallclock timestamp to all events.""" return self._enable_event_timestamps @property diff --git a/glean-core/rlb/src/configuration.rs b/glean-core/rlb/src/configuration.rs index ecce5070cd..1f786254e1 100644 --- a/glean-core/rlb/src/configuration.rs +++ b/glean-core/rlb/src/configuration.rs @@ -41,7 +41,7 @@ pub struct Configuration { pub log_level: Option, /// The rate pings may be uploaded before they are throttled. pub rate_limit: Option, - /// (Experimental) Whether to add a wallclock timestamp to all events. + /// Whether to add a wallclock timestamp to all events. pub enable_event_timestamps: bool, /// An experimentation identifier derived by the application to be sent with all pings, it should /// be noted that this has an underlying StringMetric and so should conform to the limitations that @@ -93,7 +93,7 @@ pub struct Builder { /// Optional: The internal ping upload rate limit. /// Default: `None` pub rate_limit: Option, - /// (Experimental) Whether to add a wallclock timestamp to all events. + /// Whether to add a wallclock timestamp to all events. pub enable_event_timestamps: bool, /// An experimentation identifier derived by the application to be sent with all pings, it should /// be noted that this has an underlying StringMetric and so should conform to the limitations that diff --git a/glean-core/src/lib.rs b/glean-core/src/lib.rs index f67ecd871f..1dd65c4ef3 100644 --- a/glean-core/src/lib.rs +++ b/glean-core/src/lib.rs @@ -129,7 +129,7 @@ pub struct InternalConfiguration { pub log_level: Option, /// The rate at which pings may be uploaded before they are throttled. pub rate_limit: Option, - /// (Experimental) Whether to add a wallclock timestamp to all events. + /// Whether to add a wallclock timestamp to all events. pub enable_event_timestamps: bool, /// An experimentation identifier derived by the application to be sent with all pings, it should /// be noted that this has an underlying StringMetric and so should conform to the limitations that From a0c772fd7fc6f4c605d53bf07a0b138df9f99e5d Mon Sep 17 00:00:00 2001 From: Ryan VanderMeulen Date: Mon, 3 Jun 2024 09:29:56 -0400 Subject: [PATCH 06/51] Update Gradle to version 8.8 --- CHANGELOG.md | 3 +++ gradle/wrapper/gradle-wrapper.jar | Bin 43462 -> 43453 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 2 +- 4 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 254b8d6752..94a9170624 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ [Full changelog](https://github.com/mozilla/glean/compare/v60.3.0...main) +Android + * Update to Gradle v8.8 ([#2860](https://github.com/mozilla/glean/pull/2860)) + # v60.3.0 (2024-05-31) [Full changelog](https://github.com/mozilla/glean/compare/v60.2.0...v60.3.0) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index d64cd4917707c1f8861d8cb53dd15194d4248596..e6441136f3d4ba8a0da8d277868979cfbc8ad796 100644 GIT binary patch delta 34118 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 delta 34176 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 SGW6ptSs!X7{rG*o_g?|vpSEZK diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a80b22ce5c..a4413138c9 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 1aa94a4269..b740cf1339 100755 --- a/gradlew +++ b/gradlew @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. From 42ff601578a5df84f7be721c7a667f29a0ca37c0 Mon Sep 17 00:00:00 2001 From: Ryan VanderMeulen Date: Mon, 3 Jun 2024 10:12:18 -0400 Subject: [PATCH 07/51] Sync Gradle dependencies with the versions used in AC --- CHANGELOG.md | 3 ++- gradle/libs.versions.toml | 10 +++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 94a9170624..006834ef6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,9 @@ [Full changelog](https://github.com/mozilla/glean/compare/v60.3.0...main) -Android +* Android * Update to Gradle v8.8 ([#2860](https://github.com/mozilla/glean/pull/2860)) + * Updated Kotlin to version 1.9.24 ([#2861](https://github.com/mozilla/glean/pull/2861)) # v60.3.0 (2024-05-31) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4c903f8d9f..8de85e7f70 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,7 +8,7 @@ android-plugin = "8.0.2" # Kotlin -kotlin-compiler = "1.9.23" +kotlin-compiler = "1.9.24" kotlinx-coroutines = "1.8.0" kotlinx-serialization = "1.6.3" @@ -16,9 +16,9 @@ kotlinx-serialization = "1.6.3" rust-android-gradle = "0.9.4" # AndroidX -androidx-annotation = "1.7.1" +androidx-annotation = "1.8.0" androidx-appcompat = "1.6.1" -androidx-browser = "1.7.0" +androidx-browser = "1.8.0" androidx-lifecycle = "2.7.0" androidx-work = "2.9.0" @@ -26,7 +26,7 @@ androidx-work = "2.9.0" jna = "5.14.0" # Linting and Static Analysis -detekt = "1.23.5" +detekt = "1.23.6" ktlint = "0.50.0" # AndroidX Testing @@ -38,7 +38,7 @@ androidx-test-uiautomator = "2.3.0" # Third Party Testing junit = "4.13.2" -mockito = "5.11.0" +mockito = "5.12.0" mockwebserver = "4.12.0" robolectric = "4.12.1" From 0f447e3ada1e445469577deb073d7add53b71351 Mon Sep 17 00:00:00 2001 From: Jan-Erik Rediger Date: Mon, 3 Jun 2024 11:10:49 +0200 Subject: [PATCH 08/51] Android: Delay log init until Glean is getting initialized This should avoid loading the native library early --- .dictionary | 3 ++- CHANGELOG.md | 1 + docs/dev/SUMMARY.md | 1 + docs/dev/android/logging.md | 12 ++++++++++++ .../src/main/java/mozilla/telemetry/glean/Glean.kt | 6 ++---- 5 files changed, 18 insertions(+), 5 deletions(-) create mode 100644 docs/dev/android/logging.md diff --git a/.dictionary b/.dictionary index 1f070bd431..197f334705 100644 --- a/.dictionary +++ b/.dictionary @@ -1,4 +1,4 @@ -personal_ws-1.1 en 282 utf-8 +personal_ws-1.1 en 283 utf-8 AAR AARs ABI @@ -175,6 +175,7 @@ html iMacs illumos init +initializer inlined instrumentations integrations diff --git a/CHANGELOG.md b/CHANGELOG.md index 006834ef6a..ceb7708e86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ [Full changelog](https://github.com/mozilla/glean/compare/v60.3.0...main) * Android + * Delay log init until Glean is getting initialized ([#2858](https://github.com/mozilla/glean/pull/2858)) * Update to Gradle v8.8 ([#2860](https://github.com/mozilla/glean/pull/2860)) * Updated Kotlin to version 1.9.24 ([#2861](https://github.com/mozilla/glean/pull/2861)) diff --git a/docs/dev/SUMMARY.md b/docs/dev/SUMMARY.md index 873b6bfbb4..23b729d0ea 100644 --- a/docs/dev/SUMMARY.md +++ b/docs/dev/SUMMARY.md @@ -10,6 +10,7 @@ - [Android bindings](android/index.md) - [Setup Build Environment](android/setup-android-build-environment.md) - [Android SDK/NDK versions](android/sdk-ndk-versions.md) + - [Logging](android/logging.md) - [Development with android-components](android/development-with-android-components.md) - [Locally-published components in Fenix](android/locally-published-components-in-fenix.md) - [Substituting glean_parser](android/glean-parser-substitution.md) diff --git a/docs/dev/android/logging.md b/docs/dev/android/logging.md new file mode 100644 index 0000000000..075b1822f8 --- /dev/null +++ b/docs/dev/android/logging.md @@ -0,0 +1,12 @@ +# Logging + +Logs from `glean-core` are only passed through to the Android logging framework when `Glean.initialize` is called for the first time. +This means any logging that might happen before that, e.g. from early metric collection will not be collected. + +If these logs are needed for debugging add the following initializer to `Glean.kt`: + +```kotlin +init { + gleanEnableLogging() +} +``` diff --git a/glean-core/android/src/main/java/mozilla/telemetry/glean/Glean.kt b/glean-core/android/src/main/java/mozilla/telemetry/glean/Glean.kt index 3b77cee4e7..68f03dbf1c 100644 --- a/glean-core/android/src/main/java/mozilla/telemetry/glean/Glean.kt +++ b/glean-core/android/src/main/java/mozilla/telemetry/glean/Glean.kt @@ -149,10 +149,6 @@ open class GleanInternalAPI internal constructor() { internal var isCustomDataPath: Boolean = false - init { - gleanEnableLogging() - } - /** * Initialize the Glean SDK. * @@ -185,6 +181,8 @@ open class GleanInternalAPI internal constructor() { configuration: Configuration = Configuration(), buildInfo: BuildInfo, ) { + gleanEnableLogging() + configuration.dataPath?.let { safeDataPath -> // When the `dataPath` is provided, we need to make sure: // 1. The database path provided is not `glean_data`. From 8c79d3e8a710e71defbe880e9d3a2ebc6645b316 Mon Sep 17 00:00:00 2001 From: Jan-Erik Rediger Date: Mon, 3 Jun 2024 11:09:12 +0200 Subject: [PATCH 09/51] Bump the string length limit to 255 characters --- CHANGELOG.md | 2 ++ docs/user/reference/metrics/string.md | 3 ++- .../mozilla/telemetry/glean/private/StringMetricTypeTest.kt | 2 +- glean-core/ios/GleanTests/Metrics/StringMetricTests.swift | 2 +- glean-core/python/tests/metrics/test_string.py | 2 +- glean-core/src/metrics/string.rs | 4 ++-- glean-core/tests/labeled.rs | 2 +- glean-core/tests/string.rs | 6 +++--- 8 files changed, 13 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ceb7708e86..2b4e19de7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ [Full changelog](https://github.com/mozilla/glean/compare/v60.3.0...main) +* General + * Bump the string length limit to 255 characters ([#2857](https://github.com/mozilla/glean/pull/2857)) * Android * Delay log init until Glean is getting initialized ([#2858](https://github.com/mozilla/glean/pull/2858)) * Update to Gradle v8.8 ([#2860](https://github.com/mozilla/glean/pull/2860)) diff --git a/docs/user/reference/metrics/string.md b/docs/user/reference/metrics/string.md index d9b034be6c..15a24282bc 100644 --- a/docs/user/reference/metrics/string.md +++ b/docs/user/reference/metrics/string.md @@ -121,7 +121,8 @@ Glean.searchDefault.name.set("wikipedia"); #### Limits -* Fixed maximum string length: 100. Longer strings are truncated. This is measured in the number of bytes when the string is encoded in UTF-8. +* Fixed maximum string length: 255. Longer strings are truncated. This is measured in the number of bytes when the string is encoded in UTF-8. + * Prior to Glean v60.4.0 the limit was 100 bytes. ## Testing API diff --git a/glean-core/android/src/test/java/mozilla/telemetry/glean/private/StringMetricTypeTest.kt b/glean-core/android/src/test/java/mozilla/telemetry/glean/private/StringMetricTypeTest.kt index 05113e0d4f..0af5f76630 100644 --- a/glean-core/android/src/test/java/mozilla/telemetry/glean/private/StringMetricTypeTest.kt +++ b/glean-core/android/src/test/java/mozilla/telemetry/glean/private/StringMetricTypeTest.kt @@ -124,7 +124,7 @@ class StringMetricTypeTest { ), ) - stringMetric.set("0123456789".repeat(11)) + stringMetric.set("0123456789".repeat(26)) assertEquals(1, stringMetric.testGetNumRecordedErrors(ErrorType.INVALID_OVERFLOW)) } diff --git a/glean-core/ios/GleanTests/Metrics/StringMetricTests.swift b/glean-core/ios/GleanTests/Metrics/StringMetricTests.swift index 4042932843..25385478ae 100644 --- a/glean-core/ios/GleanTests/Metrics/StringMetricTests.swift +++ b/glean-core/ios/GleanTests/Metrics/StringMetricTests.swift @@ -88,7 +88,7 @@ class StringMetricTests: XCTestCase { disabled: false )) - stringMetric.set(String(repeating: "0123456789", count: 11)) + stringMetric.set(String(repeating: "0123456789", count: 26)) XCTAssertEqual(1, stringMetric.testGetNumRecordedErrors(.invalidOverflow)) } diff --git a/glean-core/python/tests/metrics/test_string.py b/glean-core/python/tests/metrics/test_string.py index cf53258dba..c5bc118454 100644 --- a/glean-core/python/tests/metrics/test_string.py +++ b/glean-core/python/tests/metrics/test_string.py @@ -79,7 +79,7 @@ def test_setting_a_long_string_records_an_error(): ) ) - string_metric.set("0123456789" * 11) + string_metric.set("0123456789" * 26) assert 1 == string_metric.test_get_num_recorded_errors(testing.ErrorType.INVALID_OVERFLOW) diff --git a/glean-core/src/metrics/string.rs b/glean-core/src/metrics/string.rs index a56ffab648..c36f33d51c 100644 --- a/glean-core/src/metrics/string.rs +++ b/glean-core/src/metrics/string.rs @@ -13,7 +13,7 @@ use crate::util::truncate_string_at_boundary_with_error; use crate::CommonMetricData; use crate::Glean; -const MAX_LENGTH_VALUE: usize = 100; +const MAX_LENGTH_VALUE: usize = 255; /// A string metric. /// @@ -166,7 +166,7 @@ mod test { dynamic_label: None, }); - let sample_string = "0123456789".repeat(11); + let sample_string = "0123456789".repeat(26); metric.set_sync(&glean, sample_string.clone()); let truncated = truncate_string_at_boundary(sample_string, MAX_LENGTH_VALUE); diff --git a/glean-core/tests/labeled.rs b/glean-core/tests/labeled.rs index 63a7f2ccc2..c8a2511c81 100644 --- a/glean-core/tests/labeled.rs +++ b/glean-core/tests/labeled.rs @@ -162,7 +162,7 @@ fn can_record_error_for_submetric() { ); let metric = labeled.get("label1"); - metric.set_sync(&glean, "01234567890".repeat(20)); + metric.set_sync(&glean, "01234567890".repeat(26)); // Make sure that the errors have been recorded assert_eq!( diff --git a/glean-core/tests/string.rs b/glean-core/tests/string.rs index 3ccfa8f494..0daa6e6e8c 100644 --- a/glean-core/tests/string.rs +++ b/glean-core/tests/string.rs @@ -104,12 +104,12 @@ fn long_string_values_are_truncated() { ..Default::default() }); - let test_sting = "01234567890".repeat(20); - metric.set_sync(&glean, test_sting.clone()); + let test_string = "01234567890".repeat(26); + metric.set_sync(&glean, test_string.clone()); // Check that data was truncated assert_eq!( - test_sting[..100], + test_string[..255], metric.get_value(&glean, "store1").unwrap() ); From 58cddaaef8023f76c529ef10fe84b039cf1874a0 Mon Sep 17 00:00:00 2001 From: Bruno Rosa Date: Wed, 12 Jun 2024 09:28:40 -0400 Subject: [PATCH 10/51] Bug 1857411 - point to new links --- docs/user/reference/general/initializing.md | 2 +- docs/user/reference/general/toggling-upload-status.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/user/reference/general/initializing.md b/docs/user/reference/general/initializing.md index 2ca34af38c..de37dcfc42 100644 --- a/docs/user/reference/general/initializing.md +++ b/docs/user/reference/general/initializing.md @@ -505,4 +505,4 @@ describe("myTestSuite", () => { - [Swift API docs](../../../swift/Classes/Glean.html#/s:5GleanAAC10initialize13uploadEnabled13configuration9buildInfoySb_AA13ConfigurationVAA05BuildG0VtF) - [Python API docs](../../../python/glean/index.html#glean.Glean.initialize) - [Rust API docs](../../../docs/glean/fn.initialize.html) -- [JavaScript API docs](https://mozilla.github.io/glean.js/functions/core_glean.default.initialize.html) +- [JavaScript API docs](https://mozilla.github.io/glean.js/getting_started/setup/#initializing-gleanjs) diff --git a/docs/user/reference/general/toggling-upload-status.md b/docs/user/reference/general/toggling-upload-status.md index ccd852bbfc..f949a462a0 100644 --- a/docs/user/reference/general/toggling-upload-status.md +++ b/docs/user/reference/general/toggling-upload-status.md @@ -131,4 +131,4 @@ uploadSwitch.addEventListener("change", event => { * [Swift API docs](../../../swift/Classes/Glean.html#/s:5GleanAAC16setUploadEnabledyySbF) * [Python API docs](../../../python/glean/index.html#glean.Glean.set_upload_enabled) * [Rust API docs](../../../docs/glean/fn.set_upload_enabled.html) -* [JavaScript API docs](https://mozilla.github.io/glean.js/classes/core_glean.default.html#setUploadEnabled) +* [JavaScript API docs](https://mozilla.github.io/glean.js/reference/uploaders/#uploadenabled) From de1159f1c11008db5471fc04e7e2e07ac2e86a3a Mon Sep 17 00:00:00 2001 From: Bruno Rosa Date: Wed, 12 Jun 2024 09:30:30 -0400 Subject: [PATCH 11/51] Bug 1857411 - remove links that are no longer valid --- docs/user/reference/general/shutdown.md | 1 - docs/user/reference/metrics/boolean.md | 1 - docs/user/reference/metrics/counter.md | 1 - docs/user/reference/metrics/datetime.md | 1 - docs/user/reference/metrics/event.md | 1 - docs/user/reference/metrics/labeled_booleans.md | 1 - docs/user/reference/metrics/labeled_counters.md | 1 - docs/user/reference/metrics/labeled_strings.md | 1 - docs/user/reference/metrics/quantity.md | 1 - docs/user/reference/metrics/string.md | 1 - docs/user/reference/metrics/timespan.md | 1 - docs/user/reference/metrics/url.md | 4 ---- docs/user/reference/metrics/uuid.md | 1 - 13 files changed, 16 deletions(-) diff --git a/docs/user/reference/general/shutdown.md b/docs/user/reference/general/shutdown.md index f431c81318..528b971e30 100644 --- a/docs/user/reference/general/shutdown.md +++ b/docs/user/reference/general/shutdown.md @@ -74,4 +74,3 @@ is explicitly using the `webext` target. ## Reference * [Rust API docs](../../../docs/glean/fn.shutdown.html) -* [JavaScript API docs](https://mozilla.github.io/glean.js/classes/core_glean.default.html#shutdown) diff --git a/docs/user/reference/metrics/boolean.md b/docs/user/reference/metrics/boolean.md index 25ed6f7c7d..cd1d1bb165 100644 --- a/docs/user/reference/metrics/boolean.md +++ b/docs/user/reference/metrics/boolean.md @@ -289,4 +289,3 @@ N/A * [Swift API docs](../../../swift/Classes/BooleanMetricType.html) * [Python API docs](../../../python/glean/metrics/index.html#glean.metrics.BooleanMetric) * [Rust API docs](../../../docs/glean/private/boolean/struct.BooleanMetric.html) -* [JavaScript API docs](https://mozilla.github.io/glean.js/classes/core_metrics_types_boolean.default.html) diff --git a/docs/user/reference/metrics/counter.md b/docs/user/reference/metrics/counter.md index d343b5395d..abfa3d9553 100644 --- a/docs/user/reference/metrics/counter.md +++ b/docs/user/reference/metrics/counter.md @@ -336,4 +336,3 @@ N/A * [Swift API docs](../../../swift/Classes/CounterMetricType.html) * [Python API docs](../../../python/glean/metrics/index.html#glean.metrics.CounterMetric) * [Rust API docs](../../../docs/glean/private/counter/struct.CounterMetric.html) -* [JavaScript API docs](https://mozilla.github.io/glean.js/classes/core_metrics_types_counter.default.html) diff --git a/docs/user/reference/metrics/datetime.md b/docs/user/reference/metrics/datetime.md index df103a0729..f6d88458a9 100644 --- a/docs/user/reference/metrics/datetime.md +++ b/docs/user/reference/metrics/datetime.md @@ -417,4 +417,3 @@ Carefully consider the required resolution for recording your metric, and choose * [Swift API docs](../../../swift/Classes/DatetimeMetricType.html) * [Python API docs](../../../python/glean/metrics/index.html#glean.metrics.DatetimeMetricType) * [Rust API docs](../../../docs/glean/private/struct.DatetimeMetric.html) -* [Datetime API docs](https://mozilla.github.io/glean.js/classes/core_metrics_types_datetime.default.html) diff --git a/docs/user/reference/metrics/event.md b/docs/user/reference/metrics/event.md index 06d61ec531..9f4bcd3fbe 100644 --- a/docs/user/reference/metrics/event.md +++ b/docs/user/reference/metrics/event.md @@ -403,4 +403,3 @@ Each extra key contains additional metadata: * [Swift API docs](../../../swift/Classes/EventMetricType.html) * [Python API docs](../../../python/glean/metrics/index.html#glean.metrics.EventMetricType) * [Rust API docs](../../../docs/glean/private/event/struct.EventMetric.html) -* [JavaScript API docs](https://mozilla.github.io/glean.js/classes/core_metrics_types_event.default.html) diff --git a/docs/user/reference/metrics/labeled_booleans.md b/docs/user/reference/metrics/labeled_booleans.md index fbb0820bc6..acb56b2ee0 100644 --- a/docs/user/reference/metrics/labeled_booleans.md +++ b/docs/user/reference/metrics/labeled_booleans.md @@ -318,4 +318,3 @@ accessibility: * Swift API docs: [`LabeledMetricType`](../../../swift/Classes/LabeledMetricType.html), [`BooleanMetricType`](../../../swift/Classes/BooleanMetricType.html) * Python API docs: [`LabeledBooleanMetricType`](../../../python/glean/metrics/labeled.html#glean.metrics.labeled.LabeledBooleanMetricType), [`BooleanMetricType`](../../../python/glean/metrics/index.html#glean.metrics.BooleanMetric) * Rust API docs: [`LabeledMetric`](../../../docs/glean/private/struct.LabeledMetric.html), [`BooleanMetricType`](../../../docs/glean/private/struct.BooleanMetric.html) -* JavaScript API docs: [`LabeledMetricType`](https://mozilla.github.io/glean.js/classes/core_metrics_types_labeled.default.html), [`BooleanMetricType`](https://mozilla.github.io/glean.js/classes/core_metrics_types_boolean.default.html) diff --git a/docs/user/reference/metrics/labeled_counters.md b/docs/user/reference/metrics/labeled_counters.md index f89941f09f..85d69ad228 100644 --- a/docs/user/reference/metrics/labeled_counters.md +++ b/docs/user/reference/metrics/labeled_counters.md @@ -334,4 +334,3 @@ accessibility: * Swift API docs: [`LabeledMetricType`](../../../swift/Classes/LabeledMetricType.html), [`CounterMetricType`](../../../swift/Classes/CounterMetricType.html) * Python API docs: [`LabeledCounterMetricType`](../../../python/glean/metrics/labeled.html#glean.metrics.labeled.LabeledCounterMetricType), [`CounterMetricType`](../../../python/glean/metrics/index.html#glean.metrics.CounterMetric) * Rust API docs: [`LabeledMetric`](../../../docs/glean/private/struct.LabeledMetric.html), [`CounterMetricType`](../../../docs/glean/private/struct.CounterMetric.html) -* JavaScript API docs: [`LabeledMetricType`](https://mozilla.github.io/glean.js/classes/core_metrics_types_labeled.default.html), [`CounterMetricType`](https://mozilla.github.io/glean.js/classes/core_metrics_types_counter.default.html) diff --git a/docs/user/reference/metrics/labeled_strings.md b/docs/user/reference/metrics/labeled_strings.md index 15b94ca267..850b4f7b5e 100644 --- a/docs/user/reference/metrics/labeled_strings.md +++ b/docs/user/reference/metrics/labeled_strings.md @@ -307,4 +307,3 @@ login: * Swift API docs: [`LabeledMetricType`](../../../swift/Classes/LabeledMetricType.html), [`StringMetricType`](../../../swift/Classes/StringMetricType.html) * Python API docs: [`LabeledStringMetricType`](../../../python/glean/metrics/labeled.html#glean.metrics.labeled.LabeledStringMetricType), [`StringMetricType`](../../../python/glean/metrics/index.html#glean.metrics.StringMetricType) * Rust API docs: [`LabeledMetric`](../../../docs/glean/private/struct.LabeledMetric.html), [`StringMetricType`](../../../docs/glean/private/struct.StringMetric.html) -* JavaScript API docs: [`LabeledMetricType`](https://mozilla.github.io/glean.js/classes/core_metrics_types_labeled.default.html), [`StringMetricType`](https://mozilla.github.io/glean.js/classes/core_metrics_types_string.default.html) diff --git a/docs/user/reference/metrics/quantity.md b/docs/user/reference/metrics/quantity.md index eb73a814ba..1de2660b61 100644 --- a/docs/user/reference/metrics/quantity.md +++ b/docs/user/reference/metrics/quantity.md @@ -316,4 +316,3 @@ Quantities have the required `unit` parameter, which is a free-form string for d * [Swift API docs](../../../swift/Classes/QuantityMetricType.html) * [Python API docs](../../../python/glean/metrics/index.html#glean.metrics.QuantityMetric) * [Rust API docs](../../../docs/glean/private/quantity/struct.QuantityMetric.html) -* [JavaScript API docs](https://mozilla.github.io/glean.js/classes/core_metrics_types_quantity.default.html#set) diff --git a/docs/user/reference/metrics/string.md b/docs/user/reference/metrics/string.md index 15a24282bc..12d1121bb7 100644 --- a/docs/user/reference/metrics/string.md +++ b/docs/user/reference/metrics/string.md @@ -357,4 +357,3 @@ N/A * [Swift API docs](../../../swift/Classes/StringMetricType.html) * [Python API docs](../../../python/glean/metrics/string.html) * [Rust API docs](../../../docs/glean/private/struct.StringMetric.html) -* [JavaScript API docs](https://mozilla.github.io/glean.js/classes/core_metrics_types_string.default.html#set) diff --git a/docs/user/reference/metrics/timespan.md b/docs/user/reference/metrics/timespan.md index a1c907edd5..f80a5a77bd 100644 --- a/docs/user/reference/metrics/timespan.md +++ b/docs/user/reference/metrics/timespan.md @@ -685,4 +685,3 @@ and use the largest possible value that will provide useful information so as to * [Swift API docs](../../../swift/Classes/TimespanMetricType.html) * [Python API docs](../../../python/glean/metrics/index.html#glean.metrics.TimespanMetricType) * [Rust API docs](../../../docs/glean/private/struct.TimespanMetric.html) -* [JavaScript API docs](https://mozilla.github.io/glean.js/classes/core_metrics_types_timespan.default.html) diff --git a/docs/user/reference/metrics/url.md b/docs/user/reference/metrics/url.md index 4a00745cd7..2dd6fa42f8 100644 --- a/docs/user/reference/metrics/url.md +++ b/docs/user/reference/metrics/url.md @@ -355,7 +355,3 @@ N/A ## Data questions * What is the base URL used to build the search query for the search engine? - -## Reference - -* [JavaScript API docs](https://mozilla.github.io/glean.js/classes/core_metrics_types_url.default.html) diff --git a/docs/user/reference/metrics/uuid.md b/docs/user/reference/metrics/uuid.md index 8a4e602d90..1d84b4d421 100644 --- a/docs/user/reference/metrics/uuid.md +++ b/docs/user/reference/metrics/uuid.md @@ -422,4 +422,3 @@ N/A * [Swift API docs](../../../swift/Classes/UuidMetricType.html) * [Python API docs](../../../python/glean/metrics/index.html#glean.metrics.UuidMetricType) * [Rust API docs](../../../docs/glean/private/uuid/struct.UuidMetric.html) -* [JavaScript API docs](https://mozilla.github.io/glean.js/classes/core_metrics_types_uuid.default.html#set) From fcca2b2ffd5ca1fc86b1553f02d358c8ff2f4a61 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Jun 2024 05:14:47 +0000 Subject: [PATCH 12/51] Bump maturin from 1.5.0 to 1.6.0 in /glean-core/python Bumps [maturin](https://github.com/pyo3/maturin) from 1.5.0 to 1.6.0. - [Release notes](https://github.com/pyo3/maturin/releases) - [Changelog](https://github.com/PyO3/maturin/blob/main/Changelog.md) - [Commits](https://github.com/pyo3/maturin/compare/v1.5.0...v1.6.0) --- updated-dependencies: - dependency-name: maturin dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- glean-core/python/requirements_dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glean-core/python/requirements_dev.txt b/glean-core/python/requirements_dev.txt index b281fa5b85..ddd47b3beb 100644 --- a/glean-core/python/requirements_dev.txt +++ b/glean-core/python/requirements_dev.txt @@ -14,5 +14,5 @@ setuptools-git==1.2 twine==5.0.0 types-pkg_resources==0.1.3 wheel==0.42.0 -maturin==1.5.0 +maturin==1.6.0 patchelf>=0.17; sys_platform == "linux" From 676ccaa91f9e2b0f1ea5a1013233bf2f53d7d619 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Jun 2024 05:14:51 +0000 Subject: [PATCH 13/51] Bump pytest from 8.2.0 to 8.2.2 in /glean-core/python Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.2.0 to 8.2.2. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/8.2.0...8.2.2) --- updated-dependencies: - dependency-name: pytest dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- glean-core/python/requirements_dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glean-core/python/requirements_dev.txt b/glean-core/python/requirements_dev.txt index ddd47b3beb..a375ddcb95 100644 --- a/glean-core/python/requirements_dev.txt +++ b/glean-core/python/requirements_dev.txt @@ -7,7 +7,7 @@ pip pytest-localserver==0.8.0 MarkupSafe==2.0.1 pytest-runner==5.3.2 -pytest==8.2.0 +pytest==8.2.2 ruff==0.4.3 semver==2.13.0 setuptools-git==1.2 From 458d592fcfc96dca6130669b3cfd0b13b8086a46 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jun 2024 15:50:30 +0000 Subject: [PATCH 14/51] Bump ruff from 0.4.3 to 0.4.9 in /glean-core/python Bumps [ruff](https://github.com/astral-sh/ruff) from 0.4.3 to 0.4.9. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/v0.4.3...v0.4.9) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- glean-core/python/requirements_dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glean-core/python/requirements_dev.txt b/glean-core/python/requirements_dev.txt index a375ddcb95..8582b8a8d5 100644 --- a/glean-core/python/requirements_dev.txt +++ b/glean-core/python/requirements_dev.txt @@ -8,7 +8,7 @@ pytest-localserver==0.8.0 MarkupSafe==2.0.1 pytest-runner==5.3.2 pytest==8.2.2 -ruff==0.4.3 +ruff==0.4.9 semver==2.13.0 setuptools-git==1.2 twine==5.0.0 From 33e05b8cfaea6d8c2799cae1ff6295b759a01a43 Mon Sep 17 00:00:00 2001 From: Jan-Erik Rediger Date: Wed, 26 Jun 2024 12:02:01 +0200 Subject: [PATCH 15/51] CI: Run tests for all crates in the workspace --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index ee9b503fa7..cfce929247 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -66,7 +66,7 @@ commands: name: Test command: | export GLEAN_TEST_COVERAGE=$(realpath glean_coverage.txt) - cargo test --verbose --jobs 6 -- --nocapture + cargo test --workspace --verbose --jobs 6 -- --nocapture - run: name: Install required Python dependencies command: | From 0d5653394f5c875cd74eeddbe5c97bb242039d76 Mon Sep 17 00:00:00 2001 From: Jan-Erik Rediger Date: Wed, 26 Jun 2024 12:17:36 +0200 Subject: [PATCH 16/51] Add missing ping reason to test ping --- glean-core/rlb/src/test.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glean-core/rlb/src/test.rs b/glean-core/rlb/src/test.rs index e902d99ee1..e85f5f6e14 100644 --- a/glean-core/rlb/src/test.rs +++ b/glean-core/rlb/src/test.rs @@ -1444,7 +1444,7 @@ fn pings_ride_along_builtin_pings() { } let _ride_along_ping = - private::PingType::new("ride-along", true, true, true, true, true, vec![], vec![]); + private::PingType::new("ride-along", true, true, true, true, true, vec![], vec!["active".to_string()]); // Create a custom configuration to use a fake uploader. let dir = tempfile::tempdir().unwrap(); From 542a0656e27d4aad63e950edb78ac03ea3be42c7 Mon Sep 17 00:00:00 2001 From: Jan-Erik Rediger Date: Wed, 26 Jun 2024 12:23:05 +0200 Subject: [PATCH 17/51] Register ping after intializing Glean `new_glean` ends up destroying the old `Glean` object, which contains the list of known pings. So before this we registered the ping in the `Glean` object which is about to be destroyed. By creating and registering it after its been reinitalized it's known to the current instance. --- glean-core/rlb/src/test.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/glean-core/rlb/src/test.rs b/glean-core/rlb/src/test.rs index e85f5f6e14..ad4a07ad18 100644 --- a/glean-core/rlb/src/test.rs +++ b/glean-core/rlb/src/test.rs @@ -1443,9 +1443,6 @@ fn pings_ride_along_builtin_pings() { } } - let _ride_along_ping = - private::PingType::new("ride-along", true, true, true, true, true, vec![], vec!["active".to_string()]); - // Create a custom configuration to use a fake uploader. let dir = tempfile::tempdir().unwrap(); let tmpname = dir.path().to_path_buf(); @@ -1460,6 +1457,9 @@ fn pings_ride_along_builtin_pings() { let _t = new_glean(Some(cfg), true); + let _ride_along_ping = + private::PingType::new("ride-along", true, true, true, true, true, vec![], vec!["active".to_string()]); + // Simulate becoming active. handle_client_active(); From a393226942c813e6717a472cb5a48f23b3d24290 Mon Sep 17 00:00:00 2001 From: Jan-Erik Rediger Date: Wed, 26 Jun 2024 12:29:23 +0200 Subject: [PATCH 18/51] Reformatted. --- glean-core/rlb/src/test.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/glean-core/rlb/src/test.rs b/glean-core/rlb/src/test.rs index ad4a07ad18..3cc93151ef 100644 --- a/glean-core/rlb/src/test.rs +++ b/glean-core/rlb/src/test.rs @@ -1457,8 +1457,9 @@ fn pings_ride_along_builtin_pings() { let _t = new_glean(Some(cfg), true); + let reasons = vec!["active".to_string()]; let _ride_along_ping = - private::PingType::new("ride-along", true, true, true, true, true, vec![], vec!["active".to_string()]); + private::PingType::new("ride-along", true, true, true, true, true, vec![], reasons); // Simulate becoming active. handle_client_active(); From 8180068f356c861f19bb1edaa47055f0b0c2ab1c Mon Sep 17 00:00:00 2001 From: Jan-Erik Rediger Date: Wed, 26 Jun 2024 12:29:58 +0200 Subject: [PATCH 19/51] CI: Install dependencies before running tests --- .circleci/config.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index cfce929247..8a0b886e46 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -62,11 +62,6 @@ commands: - skip-if-doc-only - setup-rust-toolchain: rust-version: <> - - run: - name: Test - command: | - export GLEAN_TEST_COVERAGE=$(realpath glean_coverage.txt) - cargo test --workspace --verbose --jobs 6 -- --nocapture - run: name: Install required Python dependencies command: | @@ -74,6 +69,11 @@ commands: sudo apt install --yes --no-install-recommends \ python3-pip \ python3.10-venv + - run: + name: Test + command: | + export GLEAN_TEST_COVERAGE=$(realpath glean_coverage.txt) + cargo test --workspace --verbose --jobs 6 -- --nocapture - run: name: Run Rust sample command: | From c9b7e781fc857b375d4ce86dd9fc388dc8de0754 Mon Sep 17 00:00:00 2001 From: Jan-Erik Rediger Date: Wed, 26 Jun 2024 14:01:15 +0200 Subject: [PATCH 20/51] CI: Build arm64 Android target on TaskCluster We only build the host targets, which means we might miss issues that occur when building for the actual Android device targets --- taskcluster/kinds/android-build/kind.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/taskcluster/kinds/android-build/kind.yml b/taskcluster/kinds/android-build/kind.yml index c030e40b39..667bdc90b2 100644 --- a/taskcluster/kinds/android-build/kind.yml +++ b/taskcluster/kinds/android-build/kind.yml @@ -33,7 +33,7 @@ tasks: # and the gradle command. Another options could be to set those env vars # here like: [export, 'PATH=$HOME/.cargo/bin:$PATH' - [source, taskcluster/scripts/rustup-setup.sh] - - [bash, '-c', 'echo "rust.targets=linux-x86-64\n" > local.properties'] + - [bash, '-c', 'echo "rust.targets=linux-x86-64,arm64\n" > local.properties'] gradlew: - 'clean' - 'assembleDebugUnitTest' From a9ed1b1a54257444325789c6bf644d21ba654822 Mon Sep 17 00:00:00 2001 From: Jan-Erik Rediger Date: Wed, 26 Jun 2024 15:30:06 +0200 Subject: [PATCH 21/51] Convert test for delayed ping data flush under crashes into an external test --- .circleci/config.yml | 4 + glean-core/rlb/examples/delayed-ping-data.rs | 130 ++++++++++++++++++ glean-core/rlb/tests/persist_ping_lifetime.rs | 89 ------------ .../rlb/tests/test-delayed-ping-data.sh | 53 +++++++ 4 files changed, 187 insertions(+), 89 deletions(-) create mode 100644 glean-core/rlb/examples/delayed-ping-data.rs delete mode 100644 glean-core/rlb/tests/persist_ping_lifetime.rs create mode 100755 glean-core/rlb/tests/test-delayed-ping-data.sh diff --git a/.circleci/config.yml b/.circleci/config.yml index 8a0b886e46..f3db1ffc30 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -86,6 +86,10 @@ commands: name: Run Rust RLB crash test command: | glean-core/rlb/tests/test-thread-crashing.sh + - run: + name: Run Rust RLB delayed ping data test + command: | + glean-core/rlb/tests/test-delayed-ping-data.sh - run: name: Upload coverage report command: | diff --git a/glean-core/rlb/examples/delayed-ping-data.rs b/glean-core/rlb/examples/delayed-ping-data.rs new file mode 100644 index 0000000000..d683bf4afa --- /dev/null +++ b/glean-core/rlb/examples/delayed-ping-data.rs @@ -0,0 +1,130 @@ +// 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/. + +use std::fs::File; +use std::io::{Read, Write}; +use std::path::PathBuf; +use std::{env, process}; + +use once_cell::sync::Lazy; + +use flate2::read::GzDecoder; +use glean::net; +use glean::{private::PingType, ClientInfoMetrics, ConfigurationBuilder}; + +pub mod glean_metrics { + use glean::{private::CounterMetric, CommonMetricData, Lifetime}; + + #[allow(non_upper_case_globals)] + pub static sample_counter: once_cell::sync::Lazy = + once_cell::sync::Lazy::new(|| { + CounterMetric::new(CommonMetricData { + name: "sample_counter".into(), + category: "test.metrics".into(), + send_in_pings: vec!["prototype".into()], + disabled: false, + lifetime: Lifetime::Ping, + ..Default::default() + }) + }); +} + +#[derive(Debug)] +struct MovingUploader(PathBuf); + +impl MovingUploader { + fn new(mut path: PathBuf) -> Self { + path.push("sent_pings"); + std::fs::create_dir_all(&path).unwrap(); + Self(path) + } +} + +impl net::PingUploader for MovingUploader { + fn upload(&self, upload_request: net::PingUploadRequest) -> net::UploadResult { + let net::PingUploadRequest { + body, url, headers, .. + } = upload_request; + let mut gzip_decoder = GzDecoder::new(&body[..]); + let mut s = String::with_capacity(body.len()); + + let data = gzip_decoder + .read_to_string(&mut s) + .ok() + .map(|_| &s[..]) + .or_else(|| std::str::from_utf8(&body).ok()) + .unwrap(); + + let docid = url.rsplit('/').next().unwrap(); + let out_path = self.0.join(format!("{docid}.json")); + let mut fp = File::create(out_path).unwrap(); + + // pseudo-JSON, let's hope this works. + writeln!(fp, "{{").unwrap(); + writeln!(fp, " \"url\": {url},").unwrap(); + for (key, val) in headers { + writeln!(fp, " \"{key}\": \"{val}\",").unwrap(); + } + writeln!(fp, "}}").unwrap(); + writeln!(fp, "{data}").unwrap(); + + net::UploadResult::http_status(200) + } +} + +#[allow(non_upper_case_globals)] +pub static PrototypePing: Lazy = + Lazy::new(|| PingType::new("prototype", true, true, false, true, true, vec![], vec![])); + +fn main() { + env_logger::init(); + + let mut args = env::args().skip(1); + + let data_path = PathBuf::from(args.next().expect("need data path")); + let state = args.next().unwrap_or_default(); + + let uploader = MovingUploader::new(data_path.clone()); + let cfg = ConfigurationBuilder::new(true, data_path, "glean.pingflush") + .with_server_endpoint("invalid-test-host") + .with_use_core_mps(false) + .with_uploader(uploader) + .with_delay_ping_lifetime_io(true) + .build(); + + let client_info = ClientInfoMetrics { + app_build: env!("CARGO_PKG_VERSION").to_string(), + app_display_version: env!("CARGO_PKG_VERSION").to_string(), + channel: None, + locale: None, + }; + + glean::initialize(cfg, client_info); + + // Wait for init to finish, + // otherwise we might be to quick with calling `shutdown`. + let _ = glean_metrics::sample_counter.test_get_value(None); + + match &*state { + "accumulate_one_and_pretend_crash" => { + log::debug!("incrementing by 1. exiting without shutdown."); + glean_metrics::sample_counter.add(1) + } + "accumulate_ten_and_orderly_shutdown" => { + log::debug!("incrementing by 10, waiting, shutdown. should trigger a flush."); + glean_metrics::sample_counter.add(10); + glean::shutdown(); + } + "submit_ping" => { + log::info!("submitting PrototypePing"); + PrototypePing.submit(None); + + glean::shutdown(); + } + _ => { + eprintln!("unknown argument: {state}"); + process::exit(1); + } + } +} diff --git a/glean-core/rlb/tests/persist_ping_lifetime.rs b/glean-core/rlb/tests/persist_ping_lifetime.rs deleted file mode 100644 index f73673f46f..0000000000 --- a/glean-core/rlb/tests/persist_ping_lifetime.rs +++ /dev/null @@ -1,89 +0,0 @@ -// 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 glean::{ClientInfoMetrics, Configuration, ConfigurationBuilder}; -use std::path::PathBuf; - -/// Some user metrics. -mod metrics { - use glean::private::*; - use glean::Lifetime; - use glean_core::CommonMetricData; - use once_cell::sync::Lazy; - - #[allow(non_upper_case_globals)] - pub static boo: Lazy = Lazy::new(|| { - BooleanMetric::new(CommonMetricData { - name: "boo".into(), - category: "sample".into(), - send_in_pings: vec!["validation".into()], - lifetime: Lifetime::Ping, - disabled: false, - ..Default::default() - }) - }); -} - -fn cfg_new(tmpname: PathBuf) -> Configuration { - ConfigurationBuilder::new(true, tmpname, "firefox-desktop") - .with_server_endpoint("invalid-test-host") - .with_delay_ping_lifetime_io(true) - .build() -} - -/// Test scenario: Are ping-lifetime data persisted on shutdown when delayed? -/// -/// delay_ping_lifetime_io: true has Glean put "ping"-lifetime data in-memory -/// instead of the db. Ensure that, on orderly shutdowns, we correctly persist -/// these in-memory data to the db. -#[test] -fn delayed_ping_data() { - common::enable_test_logging(); - - metrics::boo.set(true); - - // Create a custom configuration to delay ping-lifetime io - let dir = tempfile::tempdir().unwrap(); - let tmpname = dir.path().to_path_buf(); - - common::initialize(cfg_new(tmpname.clone())); - - assert!( - metrics::boo.test_get_value(None).unwrap(), - "Data should be present. Doesn't mean it's persisted, though." - ); - - glean::test_reset_glean( - cfg_new(tmpname.clone()), - ClientInfoMetrics::unknown(), - false, - ); - - assert_eq!( - None, - metrics::boo.test_get_value(None), - "Data should not have made it to disk on unclean shutdown." - ); - metrics::boo.set(true); // Let's try again - - // This time, let's shut down cleanly - glean::shutdown(); - - // Now when we init, we should get the persisted data - glean::test_reset_glean(cfg_new(tmpname), ClientInfoMetrics::unknown(), false); - assert!( - metrics::boo.test_get_value(None).unwrap(), - "Data must be persisted between clean shutdown and init!" - ); - - glean::shutdown(); // Cleanly shut down at the end of the test. -} diff --git a/glean-core/rlb/tests/test-delayed-ping-data.sh b/glean-core/rlb/tests/test-delayed-ping-data.sh new file mode 100755 index 0000000000..84de6a69fe --- /dev/null +++ b/glean-core/rlb/tests/test-delayed-ping-data.sh @@ -0,0 +1,53 @@ +#!/bin/bash + +# Test harness for testing the RLB processes from the outside. +# +# Some behavior can only be observed when properly exiting the process running Glean, +# e.g. when an uploader runs in another thread. +# On exit the threads will be killed, regardless of their state. + +# Remove the temporary data path on all exit conditions +cleanup() { + if [ -n "$datapath" ]; then + rm -r "$datapath" + fi +} +trap cleanup INT ABRT TERM EXIT + +tmp="${TMPDIR:-/tmp}" +datapath=$(mktemp -d "${tmp}/glean_ping_lifetime_flush.XXXX") + +cmd="cargo run -p glean --example delayed-ping-data -- $datapath" + +# First run "crashes" -> no increment stored +$cmd accumulate_one_and_pretend_crash +count=$(ls -1q "$datapath/sent_pings" | wc -l) +if [[ "$count" -ne 0 ]]; then + echo "test result: FAILED." + exit 101 +fi + +# Second run increments and orderly shuts down -> increment flushed to disk. +# No ping is sent. +$cmd accumulate_ten_and_orderly_shutdown +count=$(ls -1q "$datapath/sent_pings" | wc -l) +if [[ "$count" -ne 0 ]]; then + echo "test result: FAILED." + exit 101 +fi + +# Third run sends the ping. +$cmd submit_ping +count=$(ls -1q "$datapath/sent_pings" | wc -l) +if [[ "$count" -ne 1 ]]; then + echo "test result: FAILED." + exit 101 +fi + +if ! grep -q '"test.metrics.sample_counter":10' "$datapath"/sent_pings/*; then + echo "test result: FAILED." + exit 101 +fi + +echo "test result: ok." +exit 0 From 70a8e0125f6a7bed623f10bcae84b83989f3d942 Mon Sep 17 00:00:00 2001 From: Travis Long Date: Thu, 27 Jun 2024 11:40:36 -0500 Subject: [PATCH 22/51] Update TWiG with new blog post --- docs/user/appendix/twig.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/user/appendix/twig.md b/docs/user/appendix/twig.md index 5321fefabe..e57406b581 100644 --- a/docs/user/appendix/twig.md +++ b/docs/user/appendix/twig.md @@ -67,3 +67,4 @@ They could be release notes, documentation, hopes, dreams, or whatever: so long * 2022-02-25: [Your personal Glean data pipeline](https://blog.mozilla.org/data/2022/02/25/this-week-in-glean-your-personal-glean-data-pipeline/) * 2022-10-27: [Page Load Data, Three Ways (Or, How Expensive Are Events?)](https://blog.mozilla.org/data/2022/10/27/this-week-in-glean-page-load-data-three-ways-or-how-expensive-are-events/) * 2023-05-25: [Reading “The Manager’s Path” by Camille Fournier](https://blog.mozilla.org/data/2023/05/25/this-week-in-data-reading-the-managers-path-by-camille-fournier/) +* 2024-06-27: [Cosmic Rays From Outer Space! (What Comes Next?)](https://blog.mozilla.org/data/2024/06/27/this-week-in-data-cosmic-rays-from-outer-space-what-comes-next/) From 84854fbbd66740918df732468bc6436b5d7c63ff Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 28 Jun 2024 05:55:26 +0000 Subject: [PATCH 23/51] Bump ruff from 0.4.9 to 0.5.0 in /glean-core/python Bumps [ruff](https://github.com/astral-sh/ruff) from 0.4.9 to 0.5.0. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/v0.4.9...0.5.0) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- glean-core/python/requirements_dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glean-core/python/requirements_dev.txt b/glean-core/python/requirements_dev.txt index 8582b8a8d5..bba7d59340 100644 --- a/glean-core/python/requirements_dev.txt +++ b/glean-core/python/requirements_dev.txt @@ -8,7 +8,7 @@ pytest-localserver==0.8.0 MarkupSafe==2.0.1 pytest-runner==5.3.2 pytest==8.2.2 -ruff==0.4.9 +ruff==0.5.0 semver==2.13.0 setuptools-git==1.2 twine==5.0.0 From 203bd0e0c223d9a9f5e826af5d03e9a00ee1fd63 Mon Sep 17 00:00:00 2001 From: Jan-Erik Rediger Date: Tue, 4 Jun 2024 14:20:38 +0200 Subject: [PATCH 24/51] Android: Default-enable `delayPingLifetimeIo` --- CHANGELOG.md | 1 + .../main/java/mozilla/telemetry/glean/config/Configuration.kt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b4e19de7b..7c13b2bbce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ * Delay log init until Glean is getting initialized ([#2858](https://github.com/mozilla/glean/pull/2858)) * Update to Gradle v8.8 ([#2860](https://github.com/mozilla/glean/pull/2860)) * Updated Kotlin to version 1.9.24 ([#2861](https://github.com/mozilla/glean/pull/2861)) + * Default-enable `delayPingLifetimeIo` ([#2863](https://github.com/mozilla/glean/issues/2863)) # v60.3.0 (2024-05-31) diff --git a/glean-core/android/src/main/java/mozilla/telemetry/glean/config/Configuration.kt b/glean-core/android/src/main/java/mozilla/telemetry/glean/config/Configuration.kt index 7450e0dc20..4be7c5e2a3 100644 --- a/glean-core/android/src/main/java/mozilla/telemetry/glean/config/Configuration.kt +++ b/glean-core/android/src/main/java/mozilla/telemetry/glean/config/Configuration.kt @@ -39,7 +39,7 @@ data class Configuration @JvmOverloads constructor( val enableEventTimestamps: Boolean = true, val experimentationId: String? = null, val enableInternalPings: Boolean = true, - val delayPingLifetimeIo: Boolean = false, + val delayPingLifetimeIo: Boolean = true, ) { companion object { /** From 23b8d9e195a09d0a71aac6a4d08b494f59b305ef Mon Sep 17 00:00:00 2001 From: Ryan VanderMeulen Date: Wed, 3 Jul 2024 19:37:53 -0400 Subject: [PATCH 25/51] Update AndroidX Test to the latest releases --- gradle/libs.versions.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8de85e7f70..21db8eaa07 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -30,10 +30,10 @@ detekt = "1.23.6" ktlint = "0.50.0" # AndroidX Testing -androidx-test-espresso = "3.5.1" -androidx-test-core = "1.5.0" -androidx-test-junit = "1.1.5" -androidx-test-runner = "1.5.2" +androidx-test-core = "1.6.1" +androidx-test-espresso = "3.6.1" +androidx-test-junit = "1.2.1" +androidx-test-runner = "1.6.1" androidx-test-uiautomator = "2.3.0" # Third Party Testing From 7cebced5db4470b7a64052ea1ded2c9109838197 Mon Sep 17 00:00:00 2001 From: Nicolas Guichard Date: Fri, 5 Jul 2024 12:15:55 +0200 Subject: [PATCH 26/51] Fix internal documentation link --- gradle-plugin/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle-plugin/README.md b/gradle-plugin/README.md index 9ce43e9708..9d1c4b79a9 100644 --- a/gradle-plugin/README.md +++ b/gradle-plugin/README.md @@ -1,5 +1,5 @@ This directory contains a Gradle plugin for build-time functionality, such as generating specific metrics and documentation. -See `docs/user/adding-glean-to-your-project/index.md` for information about using this +See `docs/user/user/adding-glean-to-your-project/index.md` for information about using this plugin. From 6e1e9653053b86b61f87e6dbf99509b5632e0186 Mon Sep 17 00:00:00 2001 From: Nicolas Guichard Date: Fri, 5 Jul 2024 12:19:19 +0200 Subject: [PATCH 27/51] Bug 1906365 - Gradle plugin: support using an external venv. mozilla-central uses a vendored copy of glean_parser except for the Android parts which currently download their own copy at build time through the Gradle plugin. This commit provides a way to re-use an existing Python virtualenv, in the case of mozilla-central managed by mach, instead of setting up a bespoke one. --- .../android-build-configuration-options.md | 10 ++++++++++ .../android/android-offline-builds.md | 10 +++++++++- .../glean-gradle-plugin/GleanGradlePlugin.groovy | 15 ++++++++------- 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/docs/user/language-bindings/android/android-build-configuration-options.md b/docs/user/language-bindings/android/android-build-configuration-options.md index 0adb3fb23f..f55a98d547 100644 --- a/docs/user/language-bindings/android/android-build-configuration-options.md +++ b/docs/user/language-bindings/android/android-build-configuration-options.md @@ -80,3 +80,13 @@ ext.gleanExpireByVersion = 25 Different products have different ways to compute the product version at build-time. For this reason the Glean Gradle plugin cannot provide an automated way to detect the product major version at build time. When using the expiration by version feature in Android, products must provide the major version by themselves. + +## `gleanPythonEnvDir` + +By default, the Glean Gradle plugin will manage its own Python virtualenv in `$gradleUserHomeDir/glean` to install `glean_parser`. By specifying a path in `ext.gleanPythonEnvDir` you can reuse an existing Python virtualenv. + +```groovy +ext.gleanExpireByVersion = "$buildDir/externallyManagedVenv" +``` + +`glean_parser` must be available in that virtualenv, the Gradle plugin will make no attempt at installing it. diff --git a/docs/user/language-bindings/android/android-offline-builds.md b/docs/user/language-bindings/android/android-offline-builds.md index 865b2b596b..6539a566a1 100644 --- a/docs/user/language-bindings/android/android-offline-builds.md +++ b/docs/user/language-bindings/android/android-offline-builds.md @@ -6,7 +6,15 @@ The Glean Kotlin SDK uses a Python script, [`glean_parser`](https://github.com/m For offline builds, the Python environment, and packages of `glean_parser` and its dependencies must be provided prior to building the Glean-using application. -To build a Glean-using application in offline mode, do the following: +To build a Glean-using application in offline mode, you can either: + +### Provide an externally-managed virtualenv + +Set `ext.gleanPythonEnvDir` to your existing virtualenv before applying the plugin, see [`gleanPythonEnvDir`](./android-build-configuration-options.md#gleanPythonEnvDir). + +### Provide a Python interpreter and the required wheels + +In this mode Glean will setup its own virtualenv in `$gradleUserHomeDir/glean`, controlled by the `GLEAN_PYTHON` and `GLEAN_PYTHON_WHEELS_DIR` environment variables. - Install Python 3.8 or later and ensure it's on the `PATH`. diff --git a/gradle-plugin/src/main/groovy/mozilla/telemetry/glean-gradle-plugin/GleanGradlePlugin.groovy b/gradle-plugin/src/main/groovy/mozilla/telemetry/glean-gradle-plugin/GleanGradlePlugin.groovy index 6b19a2e680..89c268a420 100644 --- a/gradle-plugin/src/main/groovy/mozilla/telemetry/glean-gradle-plugin/GleanGradlePlugin.groovy +++ b/gradle-plugin/src/main/groovy/mozilla/telemetry/glean-gradle-plugin/GleanGradlePlugin.groovy @@ -560,11 +560,12 @@ except: // builds. println("Requires glean_parser ${parserVersion}") - File envDir = setupPythonEnvironmentTasks(project, parserVersion) - // Store in both gleanCondaDir (for backward compatibility reasons) and - // the more accurate gleanPythonEnvDir variables. - project.ext.set("gleanCondaDir", envDir) - project.ext.set("gleanPythonEnvDir", envDir) + if (!project.ext.has("gleanPythonEnvDir")) { + File envDir = setupPythonEnvironmentTasks(project, parserVersion) + project.ext.set("gleanPythonEnvDir", envDir) + } + // Also store in gleanCondaDir for backward compatibility reasons + project.ext.set("gleanCondaDir", project.ext.gleanPythonEnvDir) setupExtractMetricsFromAARTasks(project) @@ -579,9 +580,9 @@ except: } if (project.android.hasProperty('applicationVariants')) { - project.android.applicationVariants.all(setupTasks(project, envDir, true, parserVersion)) + project.android.applicationVariants.all(setupTasks(project, project.ext.gleanPythonEnvDir, true, parserVersion)) } else { - project.android.libraryVariants.all(setupTasks(project, envDir, false, parserVersion)) + project.android.libraryVariants.all(setupTasks(project, project.ext.gleanPythonEnvDir, false, parserVersion)) } } } From 3a88496413276b2873aaec091de7d9d9268a397c Mon Sep 17 00:00:00 2001 From: Jan-Erik Rediger Date: Tue, 9 Jul 2024 11:05:27 +0200 Subject: [PATCH 28/51] Docs: Add virtualenv as a known word [doc only] --- .dictionary | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.dictionary b/.dictionary index 197f334705..b530852ae5 100644 --- a/.dictionary +++ b/.dictionary @@ -1,4 +1,4 @@ -personal_ws-1.1 en 283 utf-8 +personal_ws-1.1 en 284 utf-8 AAR AARs ABI @@ -271,6 +271,7 @@ urlbar validator vendored vendoring +virtualenv walkthrough walkthroughs webextension From 1abde2f08c8e20f9c1010dba7ff82d27269e4a8d Mon Sep 17 00:00:00 2001 From: Jan-Erik Rediger Date: Tue, 9 Jul 2024 11:21:57 +0200 Subject: [PATCH 29/51] Doc: Use correct link target (lowercase) [doc only] --- docs/user/language-bindings/android/android-offline-builds.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user/language-bindings/android/android-offline-builds.md b/docs/user/language-bindings/android/android-offline-builds.md index 6539a566a1..95134fdc70 100644 --- a/docs/user/language-bindings/android/android-offline-builds.md +++ b/docs/user/language-bindings/android/android-offline-builds.md @@ -10,7 +10,7 @@ To build a Glean-using application in offline mode, you can either: ### Provide an externally-managed virtualenv -Set `ext.gleanPythonEnvDir` to your existing virtualenv before applying the plugin, see [`gleanPythonEnvDir`](./android-build-configuration-options.md#gleanPythonEnvDir). +Set `ext.gleanPythonEnvDir` to your existing virtualenv before applying the plugin, see [`gleanPythonEnvDir`](./android-build-configuration-options.md#gleanpythonenvdir). ### Provide a Python interpreter and the required wheels From d6ca0f02844cd0182b85ea486d8eca899191dc78 Mon Sep 17 00:00:00 2001 From: Travis Long Date: Tue, 9 Jul 2024 10:30:37 -0500 Subject: [PATCH 30/51] Bug 1887023 - Prepare to remove `service-glean` In order to remove `service-glean` we need to first add an alias to accommodate glean_parser. We also need to make a few functions public so that they can be accessed by consumers. --- CHANGELOG.md | 1 + .../src/main/java/mozilla/telemetry/glean/Glean.kt | 10 +++++----- .../mozilla/telemetry/glean/private/HistogramBase.kt | 6 ++++++ 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c13b2bbce..5d32746da7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ * Update to Gradle v8.8 ([#2860](https://github.com/mozilla/glean/pull/2860)) * Updated Kotlin to version 1.9.24 ([#2861](https://github.com/mozilla/glean/pull/2861)) * Default-enable `delayPingLifetimeIo` ([#2863](https://github.com/mozilla/glean/issues/2863)) + * Preparing Glean to be able to remove `service-glean` from Android Components ([#2891](https://github.com/mozilla/glean/pull/2891)) # v60.3.0 (2024-05-31) diff --git a/glean-core/android/src/main/java/mozilla/telemetry/glean/Glean.kt b/glean-core/android/src/main/java/mozilla/telemetry/glean/Glean.kt index 68f03dbf1c..c45d6e71d8 100644 --- a/glean-core/android/src/main/java/mozilla/telemetry/glean/Glean.kt +++ b/glean-core/android/src/main/java/mozilla/telemetry/glean/Glean.kt @@ -540,7 +540,7 @@ open class GleanInternalAPI internal constructor() { * API synchronously. */ @VisibleForTesting(otherwise = VisibleForTesting.NONE) - internal fun enableTestingMode() { + fun enableTestingMode() { this.setTestingMode(true) } @@ -549,7 +549,7 @@ open class GleanInternalAPI internal constructor() { * This can be called by tests to change test mode on-the-fly. */ @VisibleForTesting(otherwise = VisibleForTesting.NONE) - internal fun setTestingMode(enabled: Boolean) { + fun setTestingMode(enabled: Boolean) { this.testingMode = enabled gleanSetTestMode(enabled) Dispatchers.API.setTestingMode(enabled) @@ -570,7 +570,7 @@ open class GleanInternalAPI internal constructor() { * @param uploadEnabled whether upload is enabled */ @VisibleForTesting(otherwise = VisibleForTesting.NONE) - internal fun resetGlean( + fun resetGlean( context: Context, config: Configuration, clearStores: Boolean, @@ -626,7 +626,7 @@ open class GleanInternalAPI internal constructor() { * @param port the local address to send pings to */ @VisibleForTesting(otherwise = VisibleForTesting.NONE) - internal fun testSetLocalEndpoint(port: Int) { + fun testSetLocalEndpoint(port: Int) { Glean.enableTestingMode() isSendingToTestEndpoint = true @@ -644,7 +644,7 @@ open class GleanInternalAPI internal constructor() { * @param dataPath The path to the data folder. Must be set if `clearStores` is `true`. */ @VisibleForTesting(otherwise = VisibleForTesting.NONE) - internal fun testDestroyGleanHandle(clearStores: Boolean = false, dataPath: String? = null) { + fun testDestroyGleanHandle(clearStores: Boolean = false, dataPath: String? = null) { // If it was initialized this also clears the directory gleanTestDestroyGlean(clearStores, dataPath) diff --git a/glean-core/android/src/main/java/mozilla/telemetry/glean/private/HistogramBase.kt b/glean-core/android/src/main/java/mozilla/telemetry/glean/private/HistogramBase.kt index 8dbc02c2f1..87f8d93d60 100644 --- a/glean-core/android/src/main/java/mozilla/telemetry/glean/private/HistogramBase.kt +++ b/glean-core/android/src/main/java/mozilla/telemetry/glean/private/HistogramBase.kt @@ -22,3 +22,9 @@ interface HistogramBase { */ fun accumulateSamples(samples: List) } + +// glean_parser template currently expects `HistogramMetricBase` as the name +// and since this alias was defined in `service-glean` in android-components, +// we need to keep the alias until the parser template is updated also. +// See Bug 1906941 for more information. +typealias HistogramMetricBase = mozilla.telemetry.glean.private.HistogramBase From e7ae80320683a8da2fcae07058997004a50b4fb0 Mon Sep 17 00:00:00 2001 From: Jan-Erik Rediger Date: Thu, 27 Jun 2024 12:09:03 +0200 Subject: [PATCH 31/51] Docs: Turn initialize options into a table [doc only] --- docs/user/reference/general/initializing.md | 32 ++++++++++----------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/user/reference/general/initializing.md b/docs/user/reference/general/initializing.md index de37dcfc42..3b610f2fdb 100644 --- a/docs/user/reference/general/initializing.md +++ b/docs/user/reference/general/initializing.md @@ -56,22 +56,22 @@ May only be called once. Subsequent calls to `initialize` are no-op. The available `initialize` configuration options may vary depending on the SDK. Below are listed the configuration options available on most SDKs. - -- `applicationId`: Application identifier. For Android and iOS applications, this is the id used on the platform's respective app store and is extracted automatically from the application context. -- `uploadEnabled`: The user preference on whether or not data upload is enabled. -- `appChannel`: The application's release channel. When present, the `app_channel` will be reported in all ping's [`client_info`](../../user/pings/index.html#the-client_info-section) section. -- `appBuild`: A build identifier e.g. the build identifier generated by a CI system (e.g. "1234/A"). If not present, `app_build` will be reported as "Unknown" on all pings [`client_info`](../../user/pings/index.html#the-client_info-section) section. -- `appDisplayVersion`: The user visible version string for the application running Glean. If not present, `app_display_version` will be reported as "Unknown" on all pings [`client_info`](../../user/pings/index.html#the-client_info-section) section. -- `serverEndpoint`: The server pings are sent to. Defaults to `https://incoming.telemetry.mozilla.org`. -- `maxEvents`: The maximum number of events the Glean storage will hold on to before submitting the 'events' ping. Defaults to 1 for Glean.js, 500 for all other SDKs. Refer to the [`events` ping documentation](../../user/pings/events.md) for more information on its scheduling. -- `httpUploader`: A custom HTTP uploader instance, that will overwrite Glean's provided uploader. Useful -for users that wish to use specific uploader implementations. See [Custom Uploaders](#custom-uploaders) -for more information on how and when the use this feature. -- `logLevel`: The level for how verbose the internal logging is. The level filter options in order from least to most verbose are: `Off`, `Error`, `Warn`, `Info`, `Debug`, `Trace`. See the [`log` crate docs](https://docs.rs/log/latest/log/) for more information. -- `rateLimit`: Optional. - Specifies the maximum number of pings that can be uploaded per interval of a specified number of seconds. - Default is to use the SDK default (presently 15 pings per 60s interval). -- `experimentationId`: Optional. An identifier derived by the application to be sent in all pings for the purpose of experimentation. See the experiments API documentation for more information. +Note that on some SDKs some of the options are taken as a configuration object. +Check the respective SDK documentation for details. + +| Configuration Option | Default value | Description | +| -------------------- | ------------- | ----------- | +| `applicationId` | On Android/iOS: determined automatically. Otherwise *required*. | Application identifier. For Android and iOS applications, this is the id used on the platform's respective app store and is extracted automatically from the application context. | +| `uploadEnabled` | _Required_ | The user preference on whether or not data upload is enabled. | +| `channel` | - | The application's release channel. When present, the `app_channel` will be reported in all pings' [`client_info`](../../user/pings/index.html#the-client_info-section) sections. | +| `appBuild` | On Android/iOS: determined automatically. Otherwise: - | A build identifier e.g. the build identifier generated by a CI system (e.g. "1234/A"). If not present, `app_build` will be reported as "Unknown" on all pings' [`client_info`](../../user/pings/index.html#the-client_info-section) sections. | +| `appDisplayVersion` | - | The user visible version string for the application running Glean. If not present, `app_display_version` will be reported as "Unknown" on all pings' [`client_info`](../../user/pings/index.html#the-client_info-section) sections. +| `serverEndpoint` | `https://incoming.telemetry.mozilla.org` | The server pings are sent to. +| `maxEvents` | Glean.js: 1.
    Other SDKs: 500. | The maximum number of events the Glean storage will hold on to before submitting the 'events' ping. Refer to the [`events` ping documentation](../../user/pings/events.md) for more information on its scheduling. | +| `httpUploader` | - | A custom HTTP uploader instance, that will overwrite Glean's provided uploader. Useful for users that wish to use specific uploader implementations. See [Custom Uploaders](#custom-uploaders) for more information on how and when the use this feature. | +| `logLevel` | - | The level for how verbose the internal logging is. The level filter options in order from least to most verbose are: `Off`, `Error`, `Warn`, `Info`, `Debug`, `Trace`. See the [`log` crate docs](https://docs.rs/log/latest/log/) for more information. | +| `rateLimit` | 15 pings per 60s interval | Specifies the maximum number of pings that can be uploaded per interval of a specified number of seconds. | +| `experimentationId` | - | Optional. An identifier derived by the application to be sent in all pings for the purpose of experimentation. See the experiments API documentation for more information. | To learn about SDK specific configuration options available, refer to the [Reference](#reference) section. From 87c58ab1b475e983c2ba9e2b2638ecb34382d957 Mon Sep 17 00:00:00 2001 From: Jan-Erik Rediger Date: Thu, 27 Jun 2024 12:11:23 +0200 Subject: [PATCH 32/51] Docs: Add configuration parameter in code examples [doc only] --- docs/user/reference/general/initializing.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/user/reference/general/initializing.md b/docs/user/reference/general/initializing.md index 3b610f2fdb..47ec68415c 100644 --- a/docs/user/reference/general/initializing.md +++ b/docs/user/reference/general/initializing.md @@ -112,6 +112,7 @@ class SampleApplication : Application() { // Here, `settings()` is a method to get user preferences, specific to // your application and not part of the Glean API. uploadEnabled = settings().isTelemetryEnabled, + configuration = Configuration(), buildInfo = GleanBuildInfo.buildInfo ) } @@ -179,6 +180,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // Here, `Settings` is a method to get user preferences specific to // your application, and not part of the Glean API. uploadEnabled = Settings.isTelemetryEnabled, + configuration = Configuration(), buildInfo = GleanMetrics.GleanBuild.info ) } @@ -202,7 +204,7 @@ The main control for the Glean Python SDK is on the `glean.Glean` singleton. ```python -from glean import Glean +from glean import Glean, Configuration Glean.initialize( application_id="my-app-id", @@ -210,6 +212,7 @@ Glean.initialize( # Here, `is_telemetry_enabled` is a method to get user preferences specific to # your application, and not part of the Glean API. upload_enabled=is_telemetry_enabled(), + configuration=Configuration(), ) ``` From 9880c8c8aab67657f02ff10657cb2d37e847ce8f Mon Sep 17 00:00:00 2001 From: Jan-Erik Rediger Date: Thu, 27 Jun 2024 12:14:12 +0200 Subject: [PATCH 33/51] Document additional configuration parameters [doc only] --- .dictionary | 1 + docs/user/reference/general/initializing.md | 3 +++ 2 files changed, 4 insertions(+) diff --git a/.dictionary b/.dictionary index b530852ae5..a749c32240 100644 --- a/.dictionary +++ b/.dictionary @@ -115,6 +115,7 @@ aspell async autodetected backend +backgrounding backoff backport barcode diff --git a/docs/user/reference/general/initializing.md b/docs/user/reference/general/initializing.md index 47ec68415c..d0aa3306d2 100644 --- a/docs/user/reference/general/initializing.md +++ b/docs/user/reference/general/initializing.md @@ -70,8 +70,11 @@ Check the respective SDK documentation for details. | `maxEvents` | Glean.js: 1.
    Other SDKs: 500. | The maximum number of events the Glean storage will hold on to before submitting the 'events' ping. Refer to the [`events` ping documentation](../../user/pings/events.md) for more information on its scheduling. | | `httpUploader` | - | A custom HTTP uploader instance, that will overwrite Glean's provided uploader. Useful for users that wish to use specific uploader implementations. See [Custom Uploaders](#custom-uploaders) for more information on how and when the use this feature. | | `logLevel` | - | The level for how verbose the internal logging is. The level filter options in order from least to most verbose are: `Off`, `Error`, `Warn`, `Info`, `Debug`, `Trace`. See the [`log` crate docs](https://docs.rs/log/latest/log/) for more information. | +| `enableEventTimestamps` | `true` | Whether to add a wall clock timestamp to all events. | | `rateLimit` | 15 pings per 60s interval | Specifies the maximum number of pings that can be uploaded per interval of a specified number of seconds. | | `experimentationId` | - | Optional. An identifier derived by the application to be sent in all pings for the purpose of experimentation. See the experiments API documentation for more information. | +| `enableInternalPings` | `true` | Whether to enable the internal "baseline", "events", and "metrics" pings. | +| `delayPingLifetimeIo` | `false` | Whether Glean should delay persistence of data from metrics with `ping` lifetime. On Android data is automatically persisted every 1000 writes and on backgrounding when enabled. | To learn about SDK specific configuration options available, refer to the [Reference](#reference) section. From 09a7e0927e2b1638b4589019c1ef58fd92aa32cb Mon Sep 17 00:00:00 2001 From: Arkadiusz Komarzewski Date: Wed, 10 Jul 2024 14:16:54 +0200 Subject: [PATCH 34/51] Docs: Update new Glean App bug template This adds a question about BQ access restrictions to new app bug template. --- .../user/adding-glean-to-your-project/enable-data-ingestion.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user/user/adding-glean-to-your-project/enable-data-ingestion.md b/docs/user/user/adding-glean-to-your-project/enable-data-ingestion.md index 2efb1cf666..fa2e4d8494 100644 --- a/docs/user/user/adding-glean-to-your-project/enable-data-ingestion.md +++ b/docs/user/user/adding-glean-to-your-project/enable-data-ingestion.md @@ -40,6 +40,6 @@ and be scraped for metrics as a dependency of another product. This will result in your library being added to [probe scraper](https://github.com/mozilla/probe-scraper)'s [`repositories.yaml`](https://github.com/mozilla/probe-scraper/blob/main/repositories.yaml). -[dataeng-bug]: https://bugzilla.mozilla.org/enter_bug.cgi?assigned_to=nobody%40mozilla.org&bug_ignored=0&bug_severity=--&bug_status=NEW&bug_type=task&cf_accessibility_severity=---&cf_fx_iteration=---&cf_fx_points=---&cf_status_firefox121=---&cf_status_firefox122=---&cf_status_firefox123=---&cf_tracking_firefox121=---&cf_tracking_firefox122=---&cf_tracking_firefox123=---&cf_tracking_firefox_relnote=---&comment=%2523%20To%20be%20filled%20by%20the%20requester%0D%0A%0D%0A%2A%2AApplication%20ID%5C%2A%2A%2A%3A%20my.app_id%0D%0A%2A%2AApplication%20Canonical%20Name%2A%2A%3A%20My%20Application%0D%0A%2A%2ADescription%2A%2A%3A%20Brief%20description%20of%20your%20application%0D%0A%2A%2AData-review%20response%20link%2A%2A%3A%20The%20link%20to%20the%20data%20response%20to%20the%20data%20collection%20request%20for%20adding%20Glean%20to%20your%20project.%0D%0A%2A%2ARepository%20URL%2A%2A%3A%20https%3A%2F%2Fgithub.com%2Fmozilla%2Fmy_app_name%0D%0A%2A%2ALocations%20of%20%60metrics.yaml%60%20files%20%28can%20be%20many%29%3A%2A%2A%0D%0A%20%20-%20src%2Fmetrics.yaml%0D%0A%0D%0A%2A%2ALocations%20of%20%60pings.yaml%60%20files%20%28can%20be%20many%29%3A%2A%2A%0D%0A%20-%20src%2Fpings.yaml%0D%0A%0D%0A%2A%2ADependencies%5C%2A%5C%2A%2A%2A%3A%0D%0A%20-%20glean-core%0D%0A%0D%0A%2A%2ARetention%20Days%5C%2A%5C%2A%5C%2A%2A%2A%3A%20N%0D%0A%0D%0A%2523%2523%20Notes%20and%20guidelines%0D%0A%0D%0A%5C%2A%20This%20is%20the%20identifier%20used%20to%20initialize%20Glean%20%28or%20the%20id%20used%20on%20the%20store%20on%20Android%20and%20Apple%20devices%29.%0D%0A%0D%0A%5C%2A%5C%2A%20Dependencies%20can%20be%20found%20%5Bin%20the%20Glean%20repositories%5D%28https%3A%2F%2Fprobeinfo.telemetry.mozilla.org%2Fv2%2Fglean%2Flibrary-variants%29.%20Each%20dependency%20must%20be%20listed%20explicitly.%20For%20example%2C%20the%20default%20Glean%20probes%20will%20only%20be%20included%20if%20glean%20itself%20is%20a%20dependency.%0D%0A%0D%0A%5C%2A%5C%2A%5C%2A%20Number%20of%20days%20that%20raw%20data%20will%20be%20retained.%20A%20good%20default%20is%20400.%20We%20can%20change%20this%20later%20to%20accommodate%20longer%20retention%20periods%2C%20though%20we%20cannot%20recover%20data%20that%20is%20past%20the%20retention%20period%20%28for%20example%2C%20we%20cannot%20recover%20data%20that%20is%20200%20days%20old%20if%20your%20retention%20period%20is%20180%20days%29.%0D%0A%0D%0A%2523%2523%20Need%20additional%20help%253F%0D%0AIf%20you%20need%20new%20dependencies%2C%20please%20file%20new%20bugs%20for%20them%2C%20separately%20from%20this%20one.%20For%20any%20questions%2C%20ask%20in%20the%20%2523glean%20channel.%0D%0A%0D%0A%2523%20To%20be%20filled%20by%20the%20Glean%20team%0D%0A%5B%2A%2AApplication%20friendly%20name%2A%2A%5D%28https%3A%2F%2Fmozilla.github.io%2Fprobe-scraper%2F%2523tag%2Fapplication%29%3A%20my_app_name&component=Glean%20Platform&contenttypemethod=list&contenttypeselection=text%2Fplain&defined_groups=1&filed_via=standard_form&flag_type-4=X&flag_type-607=X&flag_type-803=X&flag_type-864=X&flag_type-936=X&needinfo_role=other&needinfo_type=needinfo_from&op_sys=Unspecified&priority=--&product=Data%20Platform%20and%20Tools&rep_platform=Unspecified&short_desc=Enable%20new%20Glean%20App%20%60my.app_id%60&target_milestone=---&version=unspecified +[dataeng-bug]: https://bugzilla.mozilla.org/enter_bug.cgi?assigned_to=nobody%40mozilla.org&bug_ignored=0&bug_severity=--&bug_status=NEW&bug_type=task&cf_accessibility_severity=---&cf_fx_iteration=---&cf_fx_points=---&cf_status_firefox128=---&cf_status_firefox129=---&cf_status_firefox130=---&cf_tracking_firefox128=---&cf_tracking_firefox129=---&cf_tracking_firefox130=---&cf_tracking_firefox_relnote=---&comment=%2523%20To%20be%20filled%20by%20the%20requester%0D%0A%0D%0A%2A%2AApplication%20ID%5C%2A%2A%2A%3A%20my.app_id%0D%0A%2A%2AApplication%20Canonical%20Name%2A%2A%3A%20My%20Application%0D%0A%2A%2ADescription%2A%2A%3A%20Brief%20description%20of%20your%20application%0D%0A%2A%2AData-review%20response%20link%2A%2A%3A%20The%20link%20to%20the%20data%20response%20to%20the%20data%20collection%20request%20for%20adding%20Glean%20to%20your%20project.%0D%0A%2A%2ARepository%20URL%2A%2A%3A%20https%3A%2F%2Fgithub.com%2Fmozilla%2Fmy_app_name%0D%0A%2A%2ALocations%20of%20%60metrics.yaml%60%20files%20%28can%20be%20many%29%3A%2A%2A%0D%0A%20%20-%20src%2Fmetrics.yaml%0D%0A%0D%0A%2A%2ALocations%20of%20%60pings.yaml%60%20files%20%28can%20be%20many%29%3A%2A%2A%0D%0A%20-%20src%2Fpings.yaml%0D%0A%0D%0A%2A%2ADependencies%5C%2A%5C%2A%2A%2A%3A%0D%0A%20-%20glean-core%0D%0A%0D%0A%2A%2ARetention%20Days%5C%2A%5C%2A%5C%2A%2A%2A%3A%20N%0D%0A%0D%0A%2A%2AData%20access%20restrictions%5C%2A%5C%2A%5C%2A%5C%2A%2A%2A%3A%20No%2FYes%0D%0A%0D%0A%2523%2523%20Notes%20and%20guidelines%0D%0A%0D%0A%5C%2A%20This%20is%20the%20identifier%20used%20to%20initialize%20Glean%20%28or%20the%20id%20used%20on%20the%20store%20on%20Android%20and%20Apple%20devices%29.%0D%0A%0D%0A%5C%2A%5C%2A%20Dependencies%20can%20be%20found%20%5Bin%20the%20Glean%20repositories%5D%28https%3A%2F%2Fprobeinfo.telemetry.mozilla.org%2Fv2%2Fglean%2Flibrary-variants%29.%20Each%20dependency%20must%20be%20listed%20explicitly.%20For%20example%2C%20the%20default%20Glean%20probes%20will%20only%20be%20included%20if%20glean%20itself%20is%20a%20dependency.%0D%0A%0D%0A%5C%2A%5C%2A%5C%2A%20Number%20of%20days%20that%20raw%20data%20will%20be%20retained.%20A%20good%20default%20is%20400.%20We%20can%20change%20this%20later%20to%20accommodate%20longer%20retention%20periods%2C%20though%20we%20cannot%20recover%20data%20that%20is%20past%20the%20retention%20period%20%28for%20example%2C%20we%20cannot%20recover%20data%20that%20is%20200%20days%20old%20if%20your%20retention%20period%20is%20180%20days%29.%0D%0A%0D%0A%5C%2A%5C%2A%5C%2A%5C%2A%20Depending%20on%20classification%2C%20access%20to%20data%20might%20need%20to%20be%20restricted%20in%20BigQuery.%20If%20this%20is%20the%20case%20and%20you%20know%20that%20should%20be%20used%20-%20note%20it.%0D%0A%0D%0A%2523%2523%20Need%20additional%20help%253F%0D%0AIf%20you%20need%20new%20dependencies%2C%20please%20file%20new%20bugs%20for%20them%2C%20separately%20from%20this%20one.%20For%20any%20questions%2C%20ask%20in%20the%20%2523glean%20channel.%0D%0A%0D%0A%2523%20To%20be%20filled%20by%20the%20Glean%20team%0D%0A%5B%2A%2AApplication%20friendly%20name%2A%2A%5D%28https%3A%2F%2Fmozilla.github.io%2Fprobe-scraper%2F%2523tag%2Fapplication%29%3A%20my_app_name&component=Glean%20Platform&contenttypemethod=list&contenttypeselection=text%2Fplain&defined_groups=1&filed_via=standard_form&flag_type-4=X&flag_type-607=X&flag_type-803=X&flag_type-864=X&flag_type-936=X&needinfo_role=other&needinfo_type=needinfo_from&op_sys=Unspecified&priority=--&product=Data%20Platform%20and%20Tools&rep_platform=Unspecified&short_desc=Enable%20new%20Glean%20App%20%60my.app_id%60&target_milestone=---&version=unspecified [dataeng-bug-libraries]: https://bugzilla.mozilla.org/enter_bug.cgi?assigned_to=nobody%40mozilla.org&bug_ignored=0&bug_severity=--&bug_status=NEW&bug_type=task&cf_accessibility_severity=---&cf_fx_iteration=---&cf_fx_points=---&cf_status_firefox119=---&cf_status_firefox120=---&cf_status_firefox121=---&cf_tracking_firefox119=---&cf_tracking_firefox120=---&cf_tracking_firefox121=---&cf_tracking_firefox_relnote=---&comment=%23%20To%20be%20filled%20by%20the%20requester%0D%0A%0D%0A%2A%2ALibrary%20Canonical%20Name%2A%2A%3A%20My%20library%0D%0A%2A%2ADescription%2A%2A%3A%20Brief%20description%20of%20your%20library%0D%0A%2A%2AData-review%20response%20link%2A%2A%3A%20The%20link%20to%20the%20data%20response%20to%20the%20data%20collection%20request%20for%20adding%20Glean%20to%20your%20project%5C%2A.%0D%0A%2A%2ARepository%20URL%2A%2A%3A%20https%3A%2F%2Fgithub.com%2Fmozilla%2Fmy_app_name%0D%0A%2A%2ALocations%20of%20%60metrics.yaml%60%20files%20%28can%20be%20many%29%3A%2A%2A%0D%0A%20%20-%20src%2Fmetrics.yaml%0D%0A%0D%0A%2A%2ALocations%20of%20%60pings.yaml%60%20files%20%28can%20be%20many%29%3A%2A%2A%0D%0A%20-%20src%2Fpings.yaml%0D%0A%0D%0A%2A%2AData%20owners%3A%2A%2A%0D%0A-%20you%3F%0D%0A-%20anyone%20else%3F%0D%0A%0D%0A%2A%2ADependency%20of%2A%2A%3A%0D%0A%20-%20Fenix%0D%0A%0D%0A%23%23%20Notes%0D%0A%0D%0A%5C%2A%20These%20data-review%20requests%20need%20to%20be%20for%20the%20final%20product%20embedding%20the%20library%20and%20sending%20the%20data.%0D%0A%0D%0A%23%23%20Need%20additional%20help%3F%0D%0A%0D%0AFor%20any%20questions%2C%20ask%20in%20the%20%23glean%20channel.&component=Glean%20Platform&contenttypemethod=list&contenttypeselection=text%2Fplain&defined_groups=1&filed_via=standard_form&flag_type-4=X&flag_type-607=X&flag_type-800=X&flag_type-803=X&flag_type-864=X&flag_type-936=X&form_name=enter_bug&maketemplate=Remember%20values%20as%20bookmarkable%20template&op_sys=Unspecified&priority=--&product=Data%20Platform%20and%20Tools&rep_platform=Unspecified&short_desc=Enable%20new%20Glean%20library%20%60library-name%60&target_milestone=---&version=unspecified From f8f4ac52f13ec11dc7a1202ade1cbc76d38fd45d Mon Sep 17 00:00:00 2001 From: Jan-Erik Rediger Date: Thu, 23 May 2024 13:52:41 +0200 Subject: [PATCH 35/51] Correct argument ordering: first the time, then how far ahead that is --- glean-core/src/scheduler.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glean-core/src/scheduler.rs b/glean-core/src/scheduler.rs index 30fc956e25..d906de9160 100644 --- a/glean-core/src/scheduler.rs +++ b/glean-core/src/scheduler.rs @@ -202,7 +202,7 @@ fn start_scheduler( let mut now = now; loop { let dur = when.until(now); - log::info!("Scheduling for {:?} after {}, reason {:?}", dur, now, when); + log::info!("Scheduling for {} after {:?}, reason {:?}", now, dur, when); let mut timed_out = false; { match condvar.wait_timeout_while(cancelled_lock.lock().unwrap(), dur, |cancelled| !*cancelled) { From 06cb1a2e1102283b33085c4eb0092f03718b077c Mon Sep 17 00:00:00 2001 From: Jan-Erik Rediger Date: Tue, 21 May 2024 14:15:35 +0200 Subject: [PATCH 36/51] Collect samples of Rkv commit timing Due to this being deep within the database we need to do things slightly differently. We will measure the time ourselves and keep the samples. Then on _any_ ping assemble we record those values into the database (essentially batching those timings). Then it just goes the usual way. We _do_ want the timings to persist, because we want to collect it across a client's usage, which might include several application runs but only a single metrics ping. The downside is that recording the write times itself causes a write. --- CHANGELOG.md | 1 + .../scheduler/MetricsPingSchedulerTest.kt | 13 +++++++ glean-core/metrics.yaml | 16 +++++++++ glean-core/src/database/mod.rs | 35 +++++++++++++++---- glean-core/src/internal_metrics.rs | 15 ++++++++ glean-core/src/ping/mod.rs | 14 +++++++- glean-core/tests/ping.rs | 29 +++++++++++++++ 7 files changed, 116 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d32746da7..f6a97a42f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * General * Bump the string length limit to 255 characters ([#2857](https://github.com/mozilla/glean/pull/2857)) + * New metric `glean.database.write_time` to measure database writes ([#2845](https://github.com/mozilla/glean/pull/2845)) * Android * Delay log init until Glean is getting initialized ([#2858](https://github.com/mozilla/glean/pull/2858)) * Update to Gradle v8.8 ([#2860](https://github.com/mozilla/glean/pull/2860)) diff --git a/glean-core/android/src/test/java/mozilla/telemetry/glean/scheduler/MetricsPingSchedulerTest.kt b/glean-core/android/src/test/java/mozilla/telemetry/glean/scheduler/MetricsPingSchedulerTest.kt index b75fcd6856..df477f1310 100644 --- a/glean-core/android/src/test/java/mozilla/telemetry/glean/scheduler/MetricsPingSchedulerTest.kt +++ b/glean-core/android/src/test/java/mozilla/telemetry/glean/scheduler/MetricsPingSchedulerTest.kt @@ -280,6 +280,19 @@ class MetricsPingSchedulerTest { // Setup a test server and make Glean point to it. val server = getMockWebServer() + // Trick Glean into not sending the overdue ping which will have `glean.databse` metrics. + val fakeNow = Calendar.getInstance() + fakeNow.clear() + @Suppress("MagicNumber") // it's a fixed date only used in tests. + fakeNow.set(2015, 6, 11, 2, 0, 0) + SystemClock.setCurrentTimeMillis(fakeNow.timeInMillis) + + // Set the last sent date to yesterday. + val buildInfo = BuildInfo(versionCode = "0.0.1", versionName = "0.0.1", buildDate = Calendar.getInstance()) + val mps = MetricsPingScheduler(context, buildInfo) + + mps.updateSentDate(getISOTimeString(fakeNow, truncateTo = TimeUnit.DAY)) + resetGlean( context, Configuration( diff --git a/glean-core/metrics.yaml b/glean-core/metrics.yaml index 18040bc4ff..212cf2e88e 100644 --- a/glean-core/metrics.yaml +++ b/glean-core/metrics.yaml @@ -720,6 +720,22 @@ glean.database: - glean-team@mozilla.com expires: never + write_time: + type: timing_distribution + time_unit: microsecond + description: | + The time it takes for a write-commit for the Glean database. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1896193 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1896193#c4 + data_sensitivity: + - technical + notification_emails: + - glean-team@mozilla.com + - jrediger@mozilla.com + expires: never + glean.validation: foreground_count: type: counter diff --git a/glean-core/src/database/mod.rs b/glean-core/src/database/mod.rs index 75a068b42e..95778d8f7a 100644 --- a/glean-core/src/database/mod.rs +++ b/glean-core/src/database/mod.rs @@ -2,6 +2,7 @@ // 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::cell::RefCell; use std::collections::btree_map::Entry; use std::collections::BTreeMap; use std::fs; @@ -33,6 +34,19 @@ macro_rules! unwrap_or { }; } +macro_rules! measure_commit { + ($this:ident, $expr:expr) => {{ + let now = ::std::time::Instant::now(); + let res = $expr; + let elapsed = now.elapsed(); + if let Ok(elapsed) = elapsed.as_micros().try_into() { + let mut samples = $this.write_timings.borrow_mut(); + samples.push(elapsed); + } + res + }}; +} + /// cbindgen:ignore pub type Rkv = rkv::Rkv; /// cbindgen:ignore @@ -206,6 +220,10 @@ pub struct Database { /// RKV load state rkv_load_state: RkvLoadState, + + /// Times an Rkv write-commit took. + /// Re-applied as samples in a timing distribution later. + pub(crate) write_timings: RefCell>, } impl std::fmt::Debug for Database { @@ -274,6 +292,10 @@ impl Database { None }; + // We are gonna write, so we allocate some capacity upfront. + // The value was chosen at random. + let write_timings = RefCell::new(Vec::with_capacity(64)); + let db = Self { rkv, user_store, @@ -284,6 +306,7 @@ impl Database { ping_lifetime_count: AtomicUsize::new(0), file_size, rkv_load_state, + write_timings, }; db.load_ping_lifetime_data(); @@ -560,7 +583,7 @@ impl Database { let mut writer = self.rkv.write()?; self.get_store(lifetime) .put(&mut writer, final_key, &value)?; - writer.commit()?; + measure_commit!(self, writer.commit())?; Ok(()) } @@ -656,7 +679,7 @@ impl Database { bincode::serialize(&new_value).expect("IMPOSSIBLE: Serializing metric failed"); let value = rkv::Value::Blob(&encoded); store.put(&mut writer, final_key, &value)?; - writer.commit()?; + measure_commit!(self, writer.commit())?; Ok(()) } @@ -705,7 +728,7 @@ impl Database { } } - writer.commit()?; + measure_commit!(self, writer.commit())?; Ok(res?) }) } @@ -756,7 +779,7 @@ impl Database { } return Err(e.into()); } - writer.commit()?; + measure_commit!(self, writer.commit())?; Ok(()) }) } @@ -771,7 +794,7 @@ impl Database { pub fn clear_lifetime(&self, lifetime: Lifetime) { let res = self.write_with_store(lifetime, |mut writer, store| { store.clear(&mut writer)?; - writer.commit()?; + measure_commit!(self, writer.commit())?; Ok(()) }); @@ -840,7 +863,7 @@ impl Database { // to ping_lifetime_data. store.put(&mut writer, key, &rkv::Value::Blob(&encoded))?; } - writer.commit()?; + measure_commit!(self, writer.commit())?; Ok(()) })?; } diff --git a/glean-core/src/internal_metrics.rs b/glean-core/src/internal_metrics.rs index 3703417466..ccb9d12fa7 100644 --- a/glean-core/src/internal_metrics.rs +++ b/glean-core/src/internal_metrics.rs @@ -264,6 +264,9 @@ pub struct DatabaseMetrics { /// RKV's load result, indicating success or relaying the detected error. pub rkv_load_error: StringMetric, + + /// The time it takes for a write-commit for the Glean database. + pub write_time: TimingDistributionMetric, } impl DatabaseMetrics { @@ -289,6 +292,18 @@ impl DatabaseMetrics { disabled: false, dynamic_label: None, }), + + write_time: TimingDistributionMetric::new( + CommonMetricData { + name: "write_time".into(), + category: "glean.database".into(), + send_in_pings: vec!["metrics".into()], + lifetime: Lifetime::Ping, + disabled: false, + dynamic_label: None, + }, + TimeUnit::Microsecond, + ), } } } diff --git a/glean-core/src/ping/mod.rs b/glean-core/src/ping/mod.rs index 8af4cd27f4..f6bea44a1e 100644 --- a/glean-core/src/ping/mod.rs +++ b/glean-core/src/ping/mod.rs @@ -234,8 +234,20 @@ impl PingMaker { url_path: &'a str, ) -> Option> { info!("Collecting {}", ping.name()); + let database = glean.storage(); + + // HACK: Only for metrics pings we add the ping timings. + // But we want that to persist until the next metrics ping is actually sent. + let write_samples = database.write_timings.replace(Vec::with_capacity(64)); + if !write_samples.is_empty() { + glean + .database_metrics + .write_time + .accumulate_samples_sync(glean, &write_samples); + } + + let mut metrics_data = StorageManager.snapshot_as_json(database, ping.name(), true); - let mut metrics_data = StorageManager.snapshot_as_json(glean.storage(), ping.name(), true); let events_data = glean .event_storage() .snapshot_as_json(glean, ping.name(), true); diff --git a/glean-core/tests/ping.rs b/glean-core/tests/ping.rs index 42f93e08c7..347077a7a8 100644 --- a/glean-core/tests/ping.rs +++ b/glean-core/tests/ping.rs @@ -279,3 +279,32 @@ fn test_scheduled_pings_are_sent() { assert!(trigger_ping.submit_sync(&glean, None)); assert_eq!(2, get_queued_pings(glean.get_data_path()).unwrap().len()); } + +#[test] +fn database_write_timings_get_recorded() { + let (mut glean, _t) = new_glean(None); + + let metrics_ping = PingType::new("metrics", true, false, true, true, true, vec![], vec![]); + glean.register_ping_type(&metrics_ping); + + // We need to store a metric to record something. + let counter = CounterMetric::new(CommonMetricData { + name: "counter".into(), + category: "local".into(), + send_in_pings: vec!["metrics".into()], + ..Default::default() + }); + counter.add_sync(&glean, 1); + + assert!(metrics_ping.submit_sync(&glean, None)); + + let mut queued_pings = get_queued_pings(glean.get_data_path()).unwrap(); + assert_eq!(1, queued_pings.len(), "missing metrics ping"); + + let json = queued_pings.pop().unwrap().1; + let write_time = &json["metrics"]["timing_distribution"]["glean.database.write_time"]; + assert!( + 0 < write_time["sum"].as_i64().unwrap(), + "writing should take some time" + ); +} From 3cfd1294005117860aa8139964e91b49d88c03a0 Mon Sep 17 00:00:00 2001 From: Jan-Erik Rediger Date: Thu, 23 May 2024 13:52:14 +0200 Subject: [PATCH 37/51] RLB test: Expecting 2 pending pings now The metrics ping isn't empty upon startup, so we will send it. --- glean-core/rlb/tests/test-thread-crashing.sh | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/glean-core/rlb/tests/test-thread-crashing.sh b/glean-core/rlb/tests/test-thread-crashing.sh index 67a0f4099c..689d3fcf2f 100755 --- a/glean-core/rlb/tests/test-thread-crashing.sh +++ b/glean-core/rlb/tests/test-thread-crashing.sh @@ -15,7 +15,7 @@ cleanup() { trap cleanup INT ABRT TERM EXIT tmp="${TMPDIR:-/tmp}" -datapath=$(mktemp -d "${tmp}/glean_long_running.XXXX") +datapath=$(mktemp -d "${tmp}/crashing_threads.XXXX") RUSTFLAGS="-C panic=abort" \ RUST_LOG=debug \ @@ -23,10 +23,16 @@ cargo run -p glean --example crashing-threads -- "$datapath" ret=$? count=$(ls -1q "$datapath/pending_pings" | wc -l) -if [[ $ret -eq 0 ]] && [[ "$count" -eq 1 ]]; then +# We expect 2 pending pings: +# - a metrics ping +# - a prototype ping +if [[ $ret -eq 0 ]] && [[ "$count" -eq 2 ]]; then echo "test result: ok." exit 0 else + echo "Assertions:" + echo " ret - expected: 0, was: $ret" + echo " count - expected: 2, was: $count" echo "test result: FAILED." exit 101 fi From d2741c2fe00c0752754c8e402c3fbd7e82f1d6c0 Mon Sep 17 00:00:00 2001 From: Jan-Erik Rediger Date: Thu, 11 Jul 2024 10:57:32 +0200 Subject: [PATCH 38/51] Gradle plugin: Convert env dir to File --- .../glean-gradle-plugin/GleanGradlePlugin.groovy | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/gradle-plugin/src/main/groovy/mozilla/telemetry/glean-gradle-plugin/GleanGradlePlugin.groovy b/gradle-plugin/src/main/groovy/mozilla/telemetry/glean-gradle-plugin/GleanGradlePlugin.groovy index 89c268a420..b5f748691e 100644 --- a/gradle-plugin/src/main/groovy/mozilla/telemetry/glean-gradle-plugin/GleanGradlePlugin.groovy +++ b/gradle-plugin/src/main/groovy/mozilla/telemetry/glean-gradle-plugin/GleanGradlePlugin.groovy @@ -560,8 +560,11 @@ except: // builds. println("Requires glean_parser ${parserVersion}") - if (!project.ext.has("gleanPythonEnvDir")) { - File envDir = setupPythonEnvironmentTasks(project, parserVersion) + File envDir + if (project.ext.has("gleanPythonEnvDir")) { + envDir = new File(project.ext.gleanPythonEnvDir) + } else { + envDir = setupPythonEnvironmentTasks(project, parserVersion) project.ext.set("gleanPythonEnvDir", envDir) } // Also store in gleanCondaDir for backward compatibility reasons @@ -580,9 +583,9 @@ except: } if (project.android.hasProperty('applicationVariants')) { - project.android.applicationVariants.all(setupTasks(project, project.ext.gleanPythonEnvDir, true, parserVersion)) + project.android.applicationVariants.all(setupTasks(project, envDir, true, parserVersion)) } else { - project.android.libraryVariants.all(setupTasks(project, project.ext.gleanPythonEnvDir, false, parserVersion)) + project.android.libraryVariants.all(setupTasks(project, envDir, false, parserVersion)) } } } From f98611bc525f95e4ba05e3faaf72b61fc765dada Mon Sep 17 00:00:00 2001 From: Jan-Erik Rediger Date: Thu, 11 Jul 2024 10:59:47 +0200 Subject: [PATCH 39/51] Document gradle plugin change --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f6a97a42f8..d50f820e01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ * Updated Kotlin to version 1.9.24 ([#2861](https://github.com/mozilla/glean/pull/2861)) * Default-enable `delayPingLifetimeIo` ([#2863](https://github.com/mozilla/glean/issues/2863)) * Preparing Glean to be able to remove `service-glean` from Android Components ([#2891](https://github.com/mozilla/glean/pull/2891)) + * Gradle Plugin: Support for using an external Python environment ([#2889](https://github.com/mozilla/glean/pull/2889)) # v60.3.0 (2024-05-31) From d9d503f1a9ce620a3cf6d7b09c67e7da10476fb3 Mon Sep 17 00:00:00 2001 From: Jan-Erik Rediger Date: Thu, 11 Jul 2024 11:55:57 +0200 Subject: [PATCH 40/51] New object metric in samples to ensure we generate valid code This was broken in an older glean_parser. --- samples/android/app/metrics.yaml | 17 +++++++++++++++++ .../mozilla/samples/gleancore/MainActivity.kt | 5 +++++ .../app/glean-sample-app/ViewController.swift | 5 +++++ samples/ios/app/metrics.yaml | 18 ++++++++++++++++++ 4 files changed, 45 insertions(+) diff --git a/samples/android/app/metrics.yaml b/samples/android/app/metrics.yaml index accd16f9c7..6597c8ef96 100644 --- a/samples/android/app/metrics.yaml +++ b/samples/android/app/metrics.yaml @@ -198,3 +198,20 @@ party: type: string diameter: type: number + animals: + type: object + description: > + Contains a list of party animals + notification_emails: + - CHANGE-ME@example.com + bugs: + - https://bugzilla.mozilla.org/TODO + data_reviews: + - http://example.com/reviews + data_sensitivity: + - technical + expires: never + structure: + type: array + items: + type: string diff --git a/samples/android/app/src/main/java/org/mozilla/samples/gleancore/MainActivity.kt b/samples/android/app/src/main/java/org/mozilla/samples/gleancore/MainActivity.kt index ebf66e8e34..33530d617b 100644 --- a/samples/android/app/src/main/java/org/mozilla/samples/gleancore/MainActivity.kt +++ b/samples/android/app/src/main/java/org/mozilla/samples/gleancore/MainActivity.kt @@ -52,6 +52,11 @@ open class MainActivity : AppCompatActivity() { balloons.add(Party.BalloonsObjectItem(colour = "green")) Party.balloons.set(balloons) + val animals = Party.AnimalsObject() + animals.add("Dog") + animals.add("Cat") + Party.animals.set(animals) + // This is referencing the event ping named 'click' from the metrics.yaml file. In // order to illustrate adding extra information to the event, it is also adding to the // 'extras' field a dictionary of values. Note that the dictionary keys must be diff --git a/samples/ios/app/glean-sample-app/ViewController.swift b/samples/ios/app/glean-sample-app/ViewController.swift index f4a02e48d2..82a6ed4d6c 100644 --- a/samples/ios/app/glean-sample-app/ViewController.swift +++ b/samples/ios/app/glean-sample-app/ViewController.swift @@ -63,6 +63,11 @@ class ViewController: UIViewController { balloons.append(Party.BalloonsObjectItem(colour: "green")) Party.balloons.set(balloons) + var animals: Party.AnimalsObject = [] + animals.append("Dog") + animals.append("Cat") + Party.animals.set(animals) + // This is referencing the event ping named 'click' from the metrics.yaml file. In // order to illustrate adding extra information to the event, it is also adding to the // 'extras' field a dictionary of values. Note that the dictionary keys must be diff --git a/samples/ios/app/metrics.yaml b/samples/ios/app/metrics.yaml index c2ac64a910..6302504feb 100644 --- a/samples/ios/app/metrics.yaml +++ b/samples/ios/app/metrics.yaml @@ -187,3 +187,21 @@ party: type: string diameter: type: number + + animals: + type: object + description: > + Contains a list of party animals + notification_emails: + - CHANGE-ME@example.com + bugs: + - https://bugzilla.mozilla.org/TODO + data_reviews: + - http://example.com/reviews + data_sensitivity: + - technical + expires: never + structure: + type: array + items: + type: string From 9b7f7391328aff5f37b49d6da025eb9ff3377ef2 Mon Sep 17 00:00:00 2001 From: Jan-Erik Rediger Date: Tue, 16 Jul 2024 11:37:48 +0200 Subject: [PATCH 41/51] Gradle plugin: Force offline mode if env dir is specified --- .../telemetry/glean-gradle-plugin/GleanGradlePlugin.groovy | 1 + 1 file changed, 1 insertion(+) diff --git a/gradle-plugin/src/main/groovy/mozilla/telemetry/glean-gradle-plugin/GleanGradlePlugin.groovy b/gradle-plugin/src/main/groovy/mozilla/telemetry/glean-gradle-plugin/GleanGradlePlugin.groovy index b5f748691e..70943ea667 100644 --- a/gradle-plugin/src/main/groovy/mozilla/telemetry/glean-gradle-plugin/GleanGradlePlugin.groovy +++ b/gradle-plugin/src/main/groovy/mozilla/telemetry/glean-gradle-plugin/GleanGradlePlugin.groovy @@ -563,6 +563,7 @@ except: File envDir if (project.ext.has("gleanPythonEnvDir")) { envDir = new File(project.ext.gleanPythonEnvDir) + isOffline = true } else { envDir = setupPythonEnvironmentTasks(project, parserVersion) project.ext.set("gleanPythonEnvDir", envDir) From 232db115af857394b5e92f10ee37c9aeab9b130a Mon Sep 17 00:00:00 2001 From: Jan-Erik Rediger Date: Wed, 17 Jul 2024 14:01:09 +0200 Subject: [PATCH 42/51] Use correct parameter name in example [doc only] --- .../android/android-build-configuration-options.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user/language-bindings/android/android-build-configuration-options.md b/docs/user/language-bindings/android/android-build-configuration-options.md index f55a98d547..c8ac287b5f 100644 --- a/docs/user/language-bindings/android/android-build-configuration-options.md +++ b/docs/user/language-bindings/android/android-build-configuration-options.md @@ -86,7 +86,7 @@ When using the expiration by version feature in Android, products must provide t By default, the Glean Gradle plugin will manage its own Python virtualenv in `$gradleUserHomeDir/glean` to install `glean_parser`. By specifying a path in `ext.gleanPythonEnvDir` you can reuse an existing Python virtualenv. ```groovy -ext.gleanExpireByVersion = "$buildDir/externallyManagedVenv" +ext.gleanPythonEnvDir = "$buildDir/externallyManagedVenv" ``` `glean_parser` must be available in that virtualenv, the Gradle plugin will make no attempt at installing it. From 7e007407ed542313034f0b139b3d6401f73926d0 Mon Sep 17 00:00:00 2001 From: Chris H-C Date: Tue, 25 Jun 2024 14:43:18 -0400 Subject: [PATCH 43/51] bug 1657947 - Support labeled_*_distributions in Rust --- CHANGELOG.md | 2 + glean-core/rlb/src/lib.rs | 4 +- glean-core/src/glean.udl | 14 +- glean-core/src/internal_metrics.rs | 34 +- glean-core/src/lib.rs | 4 +- glean-core/src/lib_unit_tests.rs | 12 +- glean-core/src/metrics/custom_distribution.rs | 24 ++ glean-core/src/metrics/labeled.rs | 117 ++++++- glean-core/src/metrics/memory_distribution.rs | 18 ++ glean-core/src/metrics/mod.rs | 5 +- glean-core/src/metrics/timing_distribution.rs | 22 ++ glean-core/tests/labeled.rs | 302 +++++++++++++----- glean-core/tests/ping.rs | 17 +- 13 files changed, 443 insertions(+), 132 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d50f820e01..be5ba86905 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ * Default-enable `delayPingLifetimeIo` ([#2863](https://github.com/mozilla/glean/issues/2863)) * Preparing Glean to be able to remove `service-glean` from Android Components ([#2891](https://github.com/mozilla/glean/pull/2891)) * Gradle Plugin: Support for using an external Python environment ([#2889](https://github.com/mozilla/glean/pull/2889)) +* Rust + * New Metric Types: `labeled_custom_distribution`, `labeled_memory_distribution`, and `labeled_timing_distribution` ([bug 1657947](https://bugzilla.mozilla.org/show_bug.cgi?id=1657947)) # v60.3.0 (2024-05-31) diff --git a/glean-core/rlb/src/lib.rs b/glean-core/rlb/src/lib.rs index 3c9ab0cf53..34b3670526 100644 --- a/glean-core/rlb/src/lib.rs +++ b/glean-core/rlb/src/lib.rs @@ -36,8 +36,8 @@ pub use configuration::{Builder as ConfigurationBuilder, Configuration}; pub use core_metrics::ClientInfoMetrics; pub use glean_core::{ metrics::{Datetime, DistributionData, MemoryUnit, Rate, RecordedEvent, TimeUnit, TimerId}, - traits, CommonMetricData, Error, ErrorType, Glean, HistogramType, Lifetime, PingRateLimit, - RecordedExperiment, Result, + traits, CommonMetricData, Error, ErrorType, Glean, HistogramType, LabeledMetricData, Lifetime, + PingRateLimit, RecordedExperiment, Result, }; mod configuration; diff --git a/glean-core/src/glean.udl b/glean-core/src/glean.udl index 47affec13c..a94e0d54dc 100644 --- a/glean-core/src/glean.udl +++ b/glean-core/src/glean.udl @@ -389,8 +389,16 @@ interface StringMetric { i32 test_get_num_recorded_errors(ErrorType error); }; +[Enum] +interface LabeledMetricData { + Common(CommonMetricData cmd); + CustomDistribution(CommonMetricData cmd, i64 range_min, i64 range_max, i64 bucket_count, HistogramType histogram_type); + MemoryDistribution(CommonMetricData cmd, MemoryUnit unit); + TimingDistribution(CommonMetricData cmd, TimeUnit unit); +}; + interface LabeledCounter { - constructor(CommonMetricData meta, sequence? labels); + constructor(LabeledMetricData meta, sequence? labels); CounterMetric get(string label); @@ -398,7 +406,7 @@ interface LabeledCounter { }; interface LabeledBoolean { - constructor(CommonMetricData meta, sequence? labels); + constructor(LabeledMetricData meta, sequence? labels); BooleanMetric get(string label); @@ -406,7 +414,7 @@ interface LabeledBoolean { }; interface LabeledString { - constructor(CommonMetricData meta, sequence? labels); + constructor(LabeledMetricData meta, sequence? labels); StringMetric get(string label); diff --git a/glean-core/src/internal_metrics.rs b/glean-core/src/internal_metrics.rs index ccb9d12fa7..6ea0be1e1c 100644 --- a/glean-core/src/internal_metrics.rs +++ b/glean-core/src/internal_metrics.rs @@ -4,7 +4,7 @@ use std::borrow::Cow; -use super::{metrics::*, CommonMetricData, Lifetime}; +use super::{metrics::*, CommonMetricData, LabeledMetricData, Lifetime}; #[derive(Debug)] pub struct CoreMetrics { @@ -82,13 +82,15 @@ impl AdditionalMetrics { }), pings_submitted: LabeledMetric::::new( - CommonMetricData { - name: "pings_submitted".into(), - category: "glean.validation".into(), - send_in_pings: vec!["metrics".into(), "baseline".into()], - lifetime: Lifetime::Ping, - disabled: false, - dynamic_label: None, + LabeledMetricData::Common { + cmd: CommonMetricData { + name: "pings_submitted".into(), + category: "glean.validation".into(), + send_in_pings: vec!["metrics".into(), "baseline".into()], + lifetime: Lifetime::Ping, + disabled: false, + dynamic_label: None, + }, }, None, ), @@ -154,13 +156,15 @@ impl UploadMetrics { pub fn new() -> UploadMetrics { UploadMetrics { ping_upload_failure: LabeledMetric::::new( - CommonMetricData { - name: "ping_upload_failure".into(), - category: "glean.upload".into(), - send_in_pings: vec!["metrics".into()], - lifetime: Lifetime::Ping, - disabled: false, - dynamic_label: None, + LabeledMetricData::Common { + cmd: CommonMetricData { + name: "ping_upload_failure".into(), + category: "glean.upload".into(), + send_in_pings: vec!["metrics".into()], + lifetime: Lifetime::Ping, + disabled: false, + dynamic_label: None, + }, }, Some(vec![ Cow::from("status_code_4xx"), diff --git a/glean-core/src/lib.rs b/glean-core/src/lib.rs index 1dd65c4ef3..ad38e83ddc 100644 --- a/glean-core/src/lib.rs +++ b/glean-core/src/lib.rs @@ -63,7 +63,9 @@ pub use crate::error::{Error, ErrorKind, Result}; pub use crate::error_recording::{test_get_num_recorded_errors, ErrorType}; pub use crate::histogram::HistogramType; pub use crate::metrics::labeled::{ - AllowLabeled, LabeledBoolean, LabeledCounter, LabeledMetric, LabeledString, + AllowLabeled, LabeledBoolean, LabeledCounter, LabeledCustomDistribution, + LabeledMemoryDistribution, LabeledMetric, LabeledMetricData, LabeledString, + LabeledTimingDistribution, }; pub use crate::metrics::{ BooleanMetric, CounterMetric, CustomDistributionMetric, Datetime, DatetimeMetric, diff --git a/glean-core/src/lib_unit_tests.rs b/glean-core/src/lib_unit_tests.rs index 58a9b4eed9..635ba39736 100644 --- a/glean-core/src/lib_unit_tests.rs +++ b/glean-core/src/lib_unit_tests.rs @@ -864,11 +864,13 @@ fn test_set_remote_metric_configuration() { ..Default::default() }); let another_metric = LabeledString::new( - CommonMetricData { - category: "category".to_string(), - name: "labeled_string_metric".to_string(), - send_in_pings: vec!["baseline".to_string()], - ..Default::default() + LabeledMetricData::Common { + cmd: CommonMetricData { + category: "category".to_string(), + name: "labeled_string_metric".to_string(), + send_in_pings: vec!["baseline".to_string()], + ..Default::default() + }, }, Some(vec!["label1".into()]), ); diff --git a/glean-core/src/metrics/custom_distribution.rs b/glean-core/src/metrics/custom_distribution.rs index c7f3fbc56f..70b7707619 100644 --- a/glean-core/src/metrics/custom_distribution.rs +++ b/glean-core/src/metrics/custom_distribution.rs @@ -43,6 +43,30 @@ impl MetricType for CustomDistributionMetric { fn meta(&self) -> &CommonMetricDataInternal { &self.meta } + + fn with_name(&self, name: String) -> Self { + let mut meta = (*self.meta).clone(); + meta.inner.name = name; + Self { + meta: Arc::new(meta), + range_min: self.range_min, + range_max: self.range_max, + bucket_count: self.bucket_count, + histogram_type: self.histogram_type, + } + } + + fn with_dynamic_label(&self, label: String) -> Self { + let mut meta = (*self.meta).clone(); + meta.inner.dynamic_label = Some(label); + Self { + meta: Arc::new(meta), + range_min: self.range_min, + range_max: self.range_max, + bucket_count: self.bucket_count, + histogram_type: self.histogram_type, + } + } } // IMPORTANT: diff --git a/glean-core/src/metrics/labeled.rs b/glean-core/src/metrics/labeled.rs index f9f6a28880..7b8846e1d1 100644 --- a/glean-core/src/metrics/labeled.rs +++ b/glean-core/src/metrics/labeled.rs @@ -8,7 +8,11 @@ use std::sync::{Arc, Mutex}; use crate::common_metric_data::{CommonMetricData, CommonMetricDataInternal}; use crate::error_recording::{record_error, test_get_num_recorded_errors, ErrorType}; -use crate::metrics::{BooleanMetric, CounterMetric, Metric, MetricType, StringMetric}; +use crate::histogram::HistogramType; +use crate::metrics::{ + BooleanMetric, CounterMetric, CustomDistributionMetric, MemoryDistributionMetric, MemoryUnit, + Metric, MetricType, StringMetric, TimeUnit, TimingDistributionMetric, +}; use crate::Glean; const MAX_LABELS: usize = 16; @@ -24,6 +28,46 @@ pub type LabeledBoolean = LabeledMetric; /// A labeled string. pub type LabeledString = LabeledMetric; +/// A labeled custom_distribution. +pub type LabeledCustomDistribution = LabeledMetric; + +/// A labeled memory_distribution. +pub type LabeledMemoryDistribution = LabeledMetric; + +/// A labeled timing_distribution. +pub type LabeledTimingDistribution = LabeledMetric; + +/// The metric data needed to construct inner submetrics. +/// +/// Different Labeled metrics require different amounts and kinds of information to +/// be constructed. +pub enum LabeledMetricData { + /// The common case: just a CMD. + #[allow(missing_docs)] + Common { cmd: CommonMetricData }, + /// The custom_distribution-specific case. + #[allow(missing_docs)] + CustomDistribution { + cmd: CommonMetricData, + range_min: i64, + range_max: i64, + bucket_count: i64, + histogram_type: HistogramType, + }, + /// The memory_distribution-specific case. + #[allow(missing_docs)] + MemoryDistribution { + cmd: CommonMetricData, + unit: MemoryUnit, + }, + /// The timing_distribution-specific case. + #[allow(missing_docs)] + TimingDistribution { + cmd: CommonMetricData, + unit: TimeUnit, + }, +} + /// A labeled metric. /// /// Labeled metrics allow to record multiple sub-metrics of the same type under different string labels. @@ -43,8 +87,10 @@ pub struct LabeledMetric { /// /// We wrap it in a private module that is inaccessible outside of this module. mod private { - use crate::{ - metrics::BooleanMetric, metrics::CounterMetric, metrics::StringMetric, CommonMetricData, + use super::LabeledMetricData; + use crate::metrics::{ + BooleanMetric, CounterMetric, CustomDistributionMetric, MemoryDistributionMetric, + StringMetric, TimingDistributionMetric, }; /// The sealed labeled trait. @@ -54,24 +100,66 @@ mod private { /// `Labeled` trait. pub trait Sealed { /// Create a new `glean_core` metric from the metadata. - fn new_inner(meta: crate::CommonMetricData) -> Self; + fn new_inner(meta: LabeledMetricData) -> Self; } impl Sealed for CounterMetric { - fn new_inner(meta: CommonMetricData) -> Self { - Self::new(meta) + fn new_inner(meta: LabeledMetricData) -> Self { + match meta { + LabeledMetricData::Common { cmd } => Self::new(cmd), + _ => panic!("Incorrect construction of Labeled"), + } } } impl Sealed for BooleanMetric { - fn new_inner(meta: CommonMetricData) -> Self { - Self::new(meta) + fn new_inner(meta: LabeledMetricData) -> Self { + match meta { + LabeledMetricData::Common { cmd } => Self::new(cmd), + _ => panic!("Incorrect construction of Labeled"), + } } } impl Sealed for StringMetric { - fn new_inner(meta: CommonMetricData) -> Self { - Self::new(meta) + fn new_inner(meta: LabeledMetricData) -> Self { + match meta { + LabeledMetricData::Common { cmd } => Self::new(cmd), + _ => panic!("Incorrect construction of Labeled"), + } + } + } + + impl Sealed for CustomDistributionMetric { + fn new_inner(meta: LabeledMetricData) -> Self { + match meta { + LabeledMetricData::CustomDistribution { + cmd, + range_min, + range_max, + bucket_count, + histogram_type, + } => Self::new(cmd, range_min, range_max, bucket_count, histogram_type), + _ => panic!("Incorrect construction of Labeled"), + } + } + } + + impl Sealed for MemoryDistributionMetric { + fn new_inner(meta: LabeledMetricData) -> Self { + match meta { + LabeledMetricData::MemoryDistribution { cmd, unit } => Self::new(cmd, unit), + _ => panic!("Incorrect construction of Labeled"), + } + } + } + + impl Sealed for TimingDistributionMetric { + fn new_inner(meta: LabeledMetricData) -> Self { + match meta { + LabeledMetricData::TimingDistribution { cmd, unit } => Self::new(cmd, unit), + _ => panic!("Incorrect construction of Labeled"), + } } } } @@ -79,7 +167,7 @@ mod private { /// Trait for metrics that can be nested inside a labeled metric. pub trait AllowLabeled: MetricType { /// Create a new labeled metric. - fn new_labeled(meta: CommonMetricData) -> Self; + fn new_labeled(meta: LabeledMetricData) -> Self; } // Implement the trait for everything we marked as allowed. @@ -88,7 +176,7 @@ where T: MetricType, T: private::Sealed, { - fn new_labeled(meta: CommonMetricData) -> Self { + fn new_labeled(meta: LabeledMetricData) -> Self { T::new_inner(meta) } } @@ -100,7 +188,10 @@ where /// Creates a new labeled metric from the given metric instance and optional list of labels. /// /// See [`get`](LabeledMetric::get) for information on how static or dynamic labels are handled. - pub fn new(meta: CommonMetricData, labels: Option>>) -> LabeledMetric { + pub fn new( + meta: LabeledMetricData, + labels: Option>>, + ) -> LabeledMetric { let submetric = T::new_labeled(meta); LabeledMetric::new_inner(submetric, labels) } diff --git a/glean-core/src/metrics/memory_distribution.rs b/glean-core/src/metrics/memory_distribution.rs index 7b5e5ee192..8d04bd03c9 100644 --- a/glean-core/src/metrics/memory_distribution.rs +++ b/glean-core/src/metrics/memory_distribution.rs @@ -53,6 +53,24 @@ impl MetricType for MemoryDistributionMetric { fn meta(&self) -> &CommonMetricDataInternal { &self.meta } + + fn with_name(&self, name: String) -> Self { + let mut meta = (*self.meta).clone(); + meta.inner.name = name; + Self { + meta: Arc::new(meta), + memory_unit: self.memory_unit, + } + } + + fn with_dynamic_label(&self, label: String) -> Self { + let mut meta = (*self.meta).clone(); + meta.inner.dynamic_label = Some(label); + Self { + meta: Arc::new(meta), + memory_unit: self.memory_unit, + } + } } // IMPORTANT: diff --git a/glean-core/src/metrics/mod.rs b/glean-core/src/metrics/mod.rs index 9234fff2d1..73282c7c2a 100644 --- a/glean-core/src/metrics/mod.rs +++ b/glean-core/src/metrics/mod.rs @@ -52,7 +52,10 @@ pub use self::datetime::DatetimeMetric; pub use self::denominator::DenominatorMetric; pub use self::event::EventMetric; pub(crate) use self::experiment::ExperimentMetric; -pub use self::labeled::{LabeledBoolean, LabeledCounter, LabeledMetric, LabeledString}; +pub use self::labeled::{ + LabeledBoolean, LabeledCounter, LabeledCustomDistribution, LabeledMemoryDistribution, + LabeledMetric, LabeledString, LabeledTimingDistribution, +}; pub use self::memory_distribution::MemoryDistributionMetric; pub use self::memory_unit::MemoryUnit; pub use self::numerator::NumeratorMetric; diff --git a/glean-core/src/metrics/timing_distribution.rs b/glean-core/src/metrics/timing_distribution.rs index 28a47b1125..dc1b383ac3 100644 --- a/glean-core/src/metrics/timing_distribution.rs +++ b/glean-core/src/metrics/timing_distribution.rs @@ -85,6 +85,28 @@ impl MetricType for TimingDistributionMetric { fn meta(&self) -> &CommonMetricDataInternal { &self.meta } + + fn with_name(&self, name: String) -> Self { + let mut meta = (*self.meta).clone(); + meta.inner.name = name; + Self { + meta: Arc::new(meta), + time_unit: self.time_unit, + next_id: Arc::new(AtomicUsize::new(1)), + start_times: Arc::new(Mutex::new(Default::default())), + } + } + + fn with_dynamic_label(&self, label: String) -> Self { + let mut meta = (*self.meta).clone(); + meta.inner.dynamic_label = Some(label); + Self { + meta: Arc::new(meta), + time_unit: self.time_unit, + next_id: Arc::new(AtomicUsize::new(1)), + start_times: Arc::new(Mutex::new(Default::default())), + } + } } // IMPORTANT: diff --git a/glean-core/tests/labeled.rs b/glean-core/tests/labeled.rs index c8a2511c81..cf848728fb 100644 --- a/glean-core/tests/labeled.rs +++ b/glean-core/tests/labeled.rs @@ -10,19 +10,21 @@ use serde_json::json; use glean_core::metrics::*; use glean_core::storage::StorageManager; use glean_core::{test_get_num_recorded_errors, ErrorType}; -use glean_core::{CommonMetricData, Lifetime}; +use glean_core::{CommonMetricData, HistogramType, LabeledMetricData, Lifetime}; #[test] fn can_create_labeled_counter_metric() { let (glean, _t) = new_glean(None); let labeled = LabeledCounter::new( - CommonMetricData { - name: "labeled_metric".into(), - category: "telemetry".into(), - send_in_pings: vec!["store1".into()], - disabled: false, - lifetime: Lifetime::Ping, - ..Default::default() + LabeledMetricData::Common { + cmd: CommonMetricData { + name: "labeled_metric".into(), + category: "telemetry".into(), + send_in_pings: vec!["store1".into()], + disabled: false, + lifetime: Lifetime::Ping, + ..Default::default() + }, }, Some(vec!["label1".into()]), ); @@ -48,13 +50,15 @@ fn can_create_labeled_counter_metric() { fn can_create_labeled_string_metric() { let (glean, _t) = new_glean(None); let labeled = LabeledString::new( - CommonMetricData { - name: "labeled_metric".into(), - category: "telemetry".into(), - send_in_pings: vec!["store1".into()], - disabled: false, - lifetime: Lifetime::Ping, - ..Default::default() + LabeledMetricData::Common { + cmd: CommonMetricData { + name: "labeled_metric".into(), + category: "telemetry".into(), + send_in_pings: vec!["store1".into()], + disabled: false, + lifetime: Lifetime::Ping, + ..Default::default() + }, }, Some(vec!["label1".into()]), ); @@ -80,13 +84,15 @@ fn can_create_labeled_string_metric() { fn can_create_labeled_bool_metric() { let (glean, _t) = new_glean(None); let labeled = LabeledBoolean::new( - CommonMetricData { - name: "labeled_metric".into(), - category: "telemetry".into(), - send_in_pings: vec!["store1".into()], - disabled: false, - lifetime: Lifetime::Ping, - ..Default::default() + LabeledMetricData::Common { + cmd: CommonMetricData { + name: "labeled_metric".into(), + category: "telemetry".into(), + send_in_pings: vec!["store1".into()], + disabled: false, + lifetime: Lifetime::Ping, + ..Default::default() + }, }, Some(vec!["label1".into()]), ); @@ -108,17 +114,127 @@ fn can_create_labeled_bool_metric() { ); } +#[test] +fn can_create_labeled_custom_distribution_metric() { + let (glean, _t) = new_glean(None); + let labeled = LabeledCustomDistribution::new( + LabeledMetricData::CustomDistribution { + cmd: CommonMetricData { + name: "labeled_metric".into(), + category: "telemetry".into(), + send_in_pings: vec!["store1".into()], + disabled: false, + lifetime: Lifetime::Ping, + ..Default::default() + }, + range_min: 0, + range_max: 1024, + bucket_count: 1, + histogram_type: HistogramType::Linear, + }, + Some(vec!["label1".into()]), + ); + + let metric = labeled.get("label1"); + metric.accumulate_samples_sync(&glean, &[42]); + + let snapshot = StorageManager + .snapshot_as_json(glean.storage(), "store1", true) + .unwrap(); + + assert_eq!( + json!({ + "labeled_custom_distribution": { + "telemetry.labeled_metric": { "label1": { "sum": 42, "values": {"0": 1} } } + } + }), + snapshot + ); +} + +#[test] +fn can_create_labeled_memory_distribution_metric() { + let (glean, _t) = new_glean(None); + let labeled = LabeledMemoryDistribution::new( + LabeledMetricData::MemoryDistribution { + cmd: CommonMetricData { + name: "labeled_metric".into(), + category: "telemetry".into(), + send_in_pings: vec!["store1".into()], + disabled: false, + lifetime: Lifetime::Ping, + ..Default::default() + }, + unit: MemoryUnit::Byte, + }, + Some(vec!["label1".into()]), + ); + + let metric = labeled.get("label1"); + metric.accumulate_samples_sync(&glean, vec![42]); + + let snapshot = StorageManager + .snapshot_as_json(glean.storage(), "store1", true) + .unwrap(); + + assert_eq!( + json!({ + "labeled_memory_distribution": { + "telemetry.labeled_metric": { "label1": { "sum": 42, "values": {"41": 1, "43": 0} } } + } + }), + snapshot + ); +} + +#[test] +fn can_create_labeled_timing_distribution_metric() { + let (glean, _t) = new_glean(None); + let labeled = LabeledTimingDistribution::new( + LabeledMetricData::TimingDistribution { + cmd: CommonMetricData { + name: "labeled_metric".into(), + category: "telemetry".into(), + send_in_pings: vec!["store1".into()], + disabled: false, + lifetime: Lifetime::Ping, + ..Default::default() + }, + unit: TimeUnit::Nanosecond, + }, + Some(vec!["label1".into()]), + ); + + let metric = labeled.get("label1"); + metric.accumulate_samples_sync(&glean, &[42]); + + let snapshot = StorageManager + .snapshot_as_json(glean.storage(), "store1", true) + .unwrap(); + + assert_eq!( + json!({ + "labeled_timing_distribution": { + "telemetry.labeled_metric": { "label1": { "sum": 42, "values": {"41": 1, "45": 0} } } + } + }), + snapshot + ); +} + #[test] fn can_use_multiple_labels() { let (glean, _t) = new_glean(None); let labeled = LabeledCounter::new( - CommonMetricData { - name: "labeled_metric".into(), - category: "telemetry".into(), - send_in_pings: vec!["store1".into()], - disabled: false, - lifetime: Lifetime::Ping, - ..Default::default() + LabeledMetricData::Common { + cmd: CommonMetricData { + name: "labeled_metric".into(), + category: "telemetry".into(), + send_in_pings: vec!["store1".into()], + disabled: false, + lifetime: Lifetime::Ping, + ..Default::default() + }, }, None, ); @@ -150,13 +266,15 @@ fn can_use_multiple_labels() { fn can_record_error_for_submetric() { let (glean, _t) = new_glean(None); let labeled = LabeledString::new( - CommonMetricData { - name: "labeled_metric".into(), - category: "telemetry".into(), - send_in_pings: vec!["store1".into()], - disabled: false, - lifetime: Lifetime::Ping, - ..Default::default() + LabeledMetricData::Common { + cmd: CommonMetricData { + name: "labeled_metric".into(), + category: "telemetry".into(), + send_in_pings: vec!["store1".into()], + disabled: false, + lifetime: Lifetime::Ping, + ..Default::default() + }, }, Some(vec!["label1".into()]), ); @@ -175,13 +293,15 @@ fn can_record_error_for_submetric() { fn labels_are_checked_against_static_list() { let (glean, _t) = new_glean(None); let labeled = LabeledCounter::new( - CommonMetricData { - name: "labeled_metric".into(), - category: "telemetry".into(), - send_in_pings: vec!["store1".into()], - disabled: false, - lifetime: Lifetime::Ping, - ..Default::default() + LabeledMetricData::Common { + cmd: CommonMetricData { + name: "labeled_metric".into(), + category: "telemetry".into(), + send_in_pings: vec!["store1".into()], + disabled: false, + lifetime: Lifetime::Ping, + ..Default::default() + }, }, Some(vec!["label1".into(), "label2".into()]), ); @@ -220,13 +340,15 @@ fn labels_are_checked_against_static_list() { fn dynamic_labels_too_long() { let (glean, _t) = new_glean(None); let labeled = LabeledCounter::new( - CommonMetricData { - name: "labeled_metric".into(), - category: "telemetry".into(), - send_in_pings: vec!["store1".into()], - disabled: false, - lifetime: Lifetime::Ping, - ..Default::default() + LabeledMetricData::Common { + cmd: CommonMetricData { + name: "labeled_metric".into(), + category: "telemetry".into(), + send_in_pings: vec!["store1".into()], + disabled: false, + lifetime: Lifetime::Ping, + ..Default::default() + }, }, None, ); @@ -255,13 +377,15 @@ fn dynamic_labels_too_long() { fn dynamic_labels_regex_mismatch() { let (glean, _t) = new_glean(None); let labeled = LabeledCounter::new( - CommonMetricData { - name: "labeled_metric".into(), - category: "telemetry".into(), - send_in_pings: vec!["store1".into()], - disabled: false, - lifetime: Lifetime::Ping, - ..Default::default() + LabeledMetricData::Common { + cmd: CommonMetricData { + name: "labeled_metric".into(), + category: "telemetry".into(), + send_in_pings: vec!["store1".into()], + disabled: false, + lifetime: Lifetime::Ping, + ..Default::default() + }, }, None, ); @@ -294,13 +418,15 @@ fn dynamic_labels_regex_mismatch() { fn dynamic_labels_regex_allowed() { let (glean, _t) = new_glean(None); let labeled = LabeledCounter::new( - CommonMetricData { - name: "labeled_metric".into(), - category: "telemetry".into(), - send_in_pings: vec!["store1".into()], - disabled: false, - lifetime: Lifetime::Ping, - ..Default::default() + LabeledMetricData::Common { + cmd: CommonMetricData { + name: "labeled_metric".into(), + category: "telemetry".into(), + send_in_pings: vec!["store1".into()], + disabled: false, + lifetime: Lifetime::Ping, + ..Default::default() + }, }, None, ); @@ -349,13 +475,15 @@ fn seen_labels_get_reloaded_from_disk() { tempdir = dir; let labeled = LabeledCounter::new( - CommonMetricData { - name: "labeled_metric".into(), - category: "telemetry".into(), - send_in_pings: vec!["store1".into()], - disabled: false, - lifetime: Lifetime::Ping, - ..Default::default() + LabeledMetricData::Common { + cmd: CommonMetricData { + name: "labeled_metric".into(), + category: "telemetry".into(), + send_in_pings: vec!["store1".into()], + disabled: false, + lifetime: Lifetime::Ping, + ..Default::default() + }, }, None, ); @@ -416,13 +544,15 @@ fn seen_labels_get_reloaded_from_disk() { fn caching_metrics_with_dynamic_labels() { let (glean, _t) = new_glean(None); let labeled = LabeledCounter::new( - CommonMetricData { - name: "cached_labels".into(), - category: "telemetry".into(), - send_in_pings: vec!["store1".into()], - disabled: false, - lifetime: Lifetime::Ping, - ..Default::default() + LabeledMetricData::Common { + cmd: CommonMetricData { + name: "cached_labels".into(), + category: "telemetry".into(), + send_in_pings: vec!["store1".into()], + disabled: false, + lifetime: Lifetime::Ping, + ..Default::default() + }, }, None, ); @@ -450,13 +580,15 @@ fn caching_metrics_with_dynamic_labels() { fn caching_metrics_with_dynamic_labels_across_pings() { let (glean, _t) = new_glean(None); let labeled = LabeledCounter::new( - CommonMetricData { - name: "cached_labels2".into(), - category: "telemetry".into(), - send_in_pings: vec!["store1".into()], - disabled: false, - lifetime: Lifetime::Ping, - ..Default::default() + LabeledMetricData::Common { + cmd: CommonMetricData { + name: "cached_labels2".into(), + category: "telemetry".into(), + send_in_pings: vec!["store1".into()], + disabled: false, + lifetime: Lifetime::Ping, + ..Default::default() + }, }, None, ); diff --git a/glean-core/tests/ping.rs b/glean-core/tests/ping.rs index 347077a7a8..8c84db9835 100644 --- a/glean-core/tests/ping.rs +++ b/glean-core/tests/ping.rs @@ -9,6 +9,7 @@ use std::collections::HashMap; use glean_core::metrics::*; use glean_core::CommonMetricData; +use glean_core::LabeledMetricData; use glean_core::Lifetime; #[test] @@ -137,13 +138,15 @@ fn test_pings_submitted_metric() { // Reconstructed here so we can test it without reaching into the library // internals. let pings_submitted = LabeledCounter::new( - CommonMetricData { - name: "pings_submitted".into(), - category: "glean.validation".into(), - send_in_pings: vec!["metrics".into(), "baseline".into()], - lifetime: Lifetime::Ping, - disabled: false, - dynamic_label: None, + LabeledMetricData::Common { + cmd: CommonMetricData { + name: "pings_submitted".into(), + category: "glean.validation".into(), + send_in_pings: vec!["metrics".into(), "baseline".into()], + lifetime: Lifetime::Ping, + disabled: false, + dynamic_label: None, + }, }, None, ); From fb335bc6af7650a467dc7bba73d783f66324fd54 Mon Sep 17 00:00:00 2001 From: Chris H-C Date: Fri, 12 Jul 2024 15:20:26 -0400 Subject: [PATCH 44/51] bug 1657947 - Fix Python codegen, tests for new LabeledMetricData --- glean-core/python/glean/_loader.py | 7 +- glean-core/python/glean/metrics/__init__.py | 2 + glean-core/python/glean/metrics/labeled.py | 6 +- .../python/tests/metrics/test_labeled.py | 130 ++++++++++-------- glean-core/python/tests/test_network.py | 18 +-- 5 files changed, 94 insertions(+), 69 deletions(-) diff --git a/glean-core/python/glean/_loader.py b/glean-core/python/glean/_loader.py index 54ba3f4f80..6ca3cba7e7 100644 --- a/glean-core/python/glean/_loader.py +++ b/glean-core/python/glean/_loader.py @@ -280,7 +280,12 @@ def _get_metric_objects( if "dynamic_label" not in args: args["dynamic_label"] = None meta_args, rest = _split_ctor_args(args) - glean_metric = metric_type(metrics.CommonMetricData(**meta_args), **rest) + if getattr(metric, "labeled", False): + glean_metric = metric_type( + metrics.LabeledMetricData.COMMON(metrics.CommonMetricData(**meta_args)), **rest + ) + else: + glean_metric = metric_type(metrics.CommonMetricData(**meta_args), **rest) glean_metric.__doc__ = metric.description diff --git a/glean-core/python/glean/metrics/__init__.py b/glean-core/python/glean/metrics/__init__.py index 19cc6a39ab..896e3d8ffc 100644 --- a/glean-core/python/glean/metrics/__init__.py +++ b/glean-core/python/glean/metrics/__init__.py @@ -9,6 +9,7 @@ # Re-export utilities from .._uniffi import CommonMetricData +from .._uniffi import LabeledMetricData from .._uniffi import Lifetime from .._uniffi import MemoryUnit from .._uniffi import TimerId @@ -48,6 +49,7 @@ "EventMetricType", "LabeledBooleanMetricType", "LabeledCounterMetricType", + "LabeledMetricData", "LabeledStringMetricType", "Lifetime", "MemoryDistributionMetricType", diff --git a/glean-core/python/glean/metrics/labeled.py b/glean-core/python/glean/metrics/labeled.py index f8bd497ae3..f918e8bfc4 100644 --- a/glean-core/python/glean/metrics/labeled.py +++ b/glean-core/python/glean/metrics/labeled.py @@ -6,9 +6,9 @@ from typing import Any, Optional, Set, Type -from .._uniffi import CommonMetricData from .._uniffi import LabeledBoolean from .._uniffi import LabeledCounter +from .._uniffi import LabeledMetricData from .._uniffi import LabeledString from ..testing import ErrorType @@ -37,10 +37,10 @@ class LabeledMetricBase: def __init__( self, - common_metric_data: CommonMetricData, + labeled_metric_data: LabeledMetricData, labels: Optional[Set[str]] = None, ): - self._inner = self._ctor(common_metric_data, labels) + self._inner = self._ctor(labeled_metric_data, labels) def __getitem__(self, item: str) -> Any: """ diff --git a/glean-core/python/tests/metrics/test_labeled.py b/glean-core/python/tests/metrics/test_labeled.py index 9ddc7e2ecb..4634b8c9e8 100644 --- a/glean-core/python/tests/metrics/test_labeled.py +++ b/glean-core/python/tests/metrics/test_labeled.py @@ -5,19 +5,21 @@ from glean import Glean from glean import __version__ as glean_version from glean import metrics -from glean.metrics import Lifetime, CommonMetricData +from glean.metrics import Lifetime, CommonMetricData, LabeledMetricData from glean.testing import ErrorType def test_labeled_counter_type(ping_schema_url): labeled_counter_metric = metrics.LabeledCounterMetricType( - CommonMetricData( - disabled=False, - category="telemetry", - lifetime=Lifetime.APPLICATION, - name="labeled_counter_metric", - send_in_pings=["metrics"], - dynamic_label=None, + LabeledMetricData.COMMON( + CommonMetricData( + disabled=False, + category="telemetry", + lifetime=Lifetime.APPLICATION, + name="labeled_counter_metric", + send_in_pings=["metrics"], + dynamic_label=None, + ) ) ) @@ -31,13 +33,15 @@ def test_labeled_counter_type(ping_schema_url): def test_labeled_boolean_type(ping_schema_url): labeled_boolean_metric = metrics.LabeledBooleanMetricType( - CommonMetricData( - disabled=False, - category="telemetry", - lifetime=Lifetime.APPLICATION, - name="labeled_boolean_metric", - send_in_pings=["metrics"], - dynamic_label=None, + LabeledMetricData.COMMON( + CommonMetricData( + disabled=False, + category="telemetry", + lifetime=Lifetime.APPLICATION, + name="labeled_boolean_metric", + send_in_pings=["metrics"], + dynamic_label=None, + ) ) ) @@ -51,13 +55,15 @@ def test_labeled_boolean_type(ping_schema_url): def test_labeled_string_type(ping_schema_url): labeled_string_metric = metrics.LabeledStringMetricType( - CommonMetricData( - disabled=False, - category="telemetry", - lifetime=Lifetime.APPLICATION, - name="labeled_string_metric", - send_in_pings=["metrics"], - dynamic_label=None, + LabeledMetricData.COMMON( + CommonMetricData( + disabled=False, + category="telemetry", + lifetime=Lifetime.APPLICATION, + name="labeled_string_metric", + send_in_pings=["metrics"], + dynamic_label=None, + ) ) ) @@ -71,13 +77,15 @@ def test_labeled_string_type(ping_schema_url): def test_other_label_with_predefined_labels(ping_schema_url): labeled_counter_metric = metrics.LabeledCounterMetricType( - CommonMetricData( - disabled=False, - category="telemetry", - lifetime=Lifetime.APPLICATION, - name="labeled_counter_metric", - send_in_pings=["metrics"], - dynamic_label=None, + LabeledMetricData.COMMON( + CommonMetricData( + disabled=False, + category="telemetry", + lifetime=Lifetime.APPLICATION, + name="labeled_counter_metric", + send_in_pings=["metrics"], + dynamic_label=None, + ) ), labels=["foo", "bar", "baz"], ) @@ -97,13 +105,15 @@ def test_other_label_with_predefined_labels(ping_schema_url): def test_other_label_without_predefined_labels(ping_schema_url): labeled_counter_metric = metrics.LabeledCounterMetricType( - CommonMetricData( - disabled=False, - category="telemetry", - lifetime=Lifetime.APPLICATION, - name="labeled_counter_metric", - send_in_pings=["metrics"], - dynamic_label=None, + LabeledMetricData.COMMON( + CommonMetricData( + disabled=False, + category="telemetry", + lifetime=Lifetime.APPLICATION, + name="labeled_counter_metric", + send_in_pings=["metrics"], + dynamic_label=None, + ) ) ) @@ -120,13 +130,15 @@ def test_other_label_without_predefined_labels(ping_schema_url): def test_other_label_without_predefined_labels_before_glean_init(): labeled_counter_metric = metrics.LabeledCounterMetricType( - CommonMetricData( - disabled=False, - category="telemetry", - lifetime=Lifetime.APPLICATION, - name="labeled_counter_metric", - send_in_pings=["metrics"], - dynamic_label=None, + LabeledMetricData.COMMON( + CommonMetricData( + disabled=False, + category="telemetry", + lifetime=Lifetime.APPLICATION, + name="labeled_counter_metric", + send_in_pings=["metrics"], + dynamic_label=None, + ) ) ) @@ -150,13 +162,15 @@ def test_other_label_without_predefined_labels_before_glean_init(): def test_invalid_labels_go_to_other(): labeled_counter_metric = metrics.LabeledCounterMetricType( - CommonMetricData( - disabled=False, - category="telemetry", - lifetime=Lifetime.APPLICATION, - name="labeled_counter_metric", - send_in_pings=["metrics"], - dynamic_label=None, + LabeledMetricData.COMMON( + CommonMetricData( + disabled=False, + category="telemetry", + lifetime=Lifetime.APPLICATION, + name="labeled_counter_metric", + send_in_pings=["metrics"], + dynamic_label=None, + ) ) ) @@ -186,13 +200,15 @@ def test_rapidly_recreating_labeled_metrics_does_not_crash(): """ labeled_counter_metric = metrics.LabeledCounterMetricType( - CommonMetricData( - category="telemetry", - name="labeled_nocrash", - send_in_pings=["metrics"], - lifetime=Lifetime.APPLICATION, - disabled=False, - dynamic_label=None, + LabeledMetricData.COMMON( + CommonMetricData( + category="telemetry", + name="labeled_nocrash", + send_in_pings=["metrics"], + lifetime=Lifetime.APPLICATION, + disabled=False, + dynamic_label=None, + ) ), labels=["foo"], ) diff --git a/glean-core/python/tests/test_network.py b/glean-core/python/tests/test_network.py index 8c2ab98568..6aba510ac4 100644 --- a/glean-core/python/tests/test_network.py +++ b/glean-core/python/tests/test_network.py @@ -13,7 +13,7 @@ from glean import Glean from glean import _builtins from glean import metrics -from glean.metrics import CounterMetricType, Lifetime, CommonMetricData +from glean.metrics import CounterMetricType, Lifetime, CommonMetricData, LabeledMetricData from glean._process_dispatcher import ProcessDispatcher from glean.net import PingUploadWorker from glean.net.http_client import HttpClientUploader @@ -26,13 +26,15 @@ def get_upload_failure_metric(): return metrics.LabeledCounterMetricType( - CommonMetricData( - disabled=False, - send_in_pings=["metrics"], - name="ping_upload_failure", - category="glean.upload", - lifetime=metrics.Lifetime.PING, - dynamic_label=None, + LabeledMetricData.COMMON( + CommonMetricData( + disabled=False, + send_in_pings=["metrics"], + name="ping_upload_failure", + category="glean.upload", + lifetime=metrics.Lifetime.PING, + dynamic_label=None, + ) ), labels=[ "status_code_4xx", From 5737a3b137723784bf16a16884bbdf0304d18858 Mon Sep 17 00:00:00 2001 From: Chris H-C Date: Fri, 12 Jul 2024 15:50:56 -0400 Subject: [PATCH 45/51] bug 1657947 - Update Kotlin to use new LabeledMetricData --- .../mozilla/telemetry/glean/private/Aliases.kt | 10 ++++++++++ .../telemetry/glean/private/LabeledMetricType.kt | 14 ++++++++------ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/glean-core/android/src/main/java/mozilla/telemetry/glean/private/Aliases.kt b/glean-core/android/src/main/java/mozilla/telemetry/glean/private/Aliases.kt index 3e1afce5c8..0d14644aed 100644 --- a/glean-core/android/src/main/java/mozilla/telemetry/glean/private/Aliases.kt +++ b/glean-core/android/src/main/java/mozilla/telemetry/glean/private/Aliases.kt @@ -55,3 +55,13 @@ typealias RecordedExperiment = mozilla.telemetry.glean.internal.RecordedExperime * A rate value as given by its numerator and denominator. */ typealias Rate = mozilla.telemetry.glean.internal.Rate + +/** + * The set of data needed to construct labeled metric types. + */ +typealias LabeledMetricData = mozilla.telemetry.glean.internal.LabeledMetricData + +/** + * The set of data specifically needed to construct simple labeled metric types. + */ +typealias CommonLabeledMetricData = mozilla.telemetry.glean.internal.LabeledMetricData.Common diff --git a/glean-core/android/src/main/java/mozilla/telemetry/glean/private/LabeledMetricType.kt b/glean-core/android/src/main/java/mozilla/telemetry/glean/private/LabeledMetricType.kt index 20940f8f8a..cd32c82b2c 100644 --- a/glean-core/android/src/main/java/mozilla/telemetry/glean/private/LabeledMetricType.kt +++ b/glean-core/android/src/main/java/mozilla/telemetry/glean/private/LabeledMetricType.kt @@ -36,12 +36,14 @@ class LabeledMetricType( private val inner: Any init { - val meta = CommonMetricData( - category = category, - name = name, - sendInPings = sendInPings, - disabled = disabled, - lifetime = lifetime, + val meta = CommonLabeledMetricData( + cmd = CommonMetricData( + category = category, + name = name, + sendInPings = sendInPings, + disabled = disabled, + lifetime = lifetime, + ), ) this.inner = when (subMetric) { From 9f8b8c111798a2c91f198e32dbf6004deaeb1b63 Mon Sep 17 00:00:00 2001 From: Chris H-C Date: Fri, 12 Jul 2024 16:00:54 -0400 Subject: [PATCH 46/51] bug 1657947 - Adapt Swift to LabeledMetricData --- glean-core/ios/Glean/Metrics/LabeledMetric.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/glean-core/ios/Glean/Metrics/LabeledMetric.swift b/glean-core/ios/Glean/Metrics/LabeledMetric.swift index ff148b9663..040c2dda3d 100644 --- a/glean-core/ios/Glean/Metrics/LabeledMetric.swift +++ b/glean-core/ios/Glean/Metrics/LabeledMetric.swift @@ -46,11 +46,11 @@ public class LabeledMetricType { switch subMetric { case is CounterMetricType: - self.inner = LabeledCounter(meta, labels) + self.inner = LabeledCounter(.common(cmd: meta), labels) case is BooleanMetricType: - self.inner = LabeledBoolean(meta, labels) + self.inner = LabeledBoolean(.common(cmd: meta), labels) case is StringMetricType: - self.inner = LabeledString(meta, labels) + self.inner = LabeledString(.common(cmd: meta), labels) default: throw "Can not create a labeled version of this metric type" } From 12a0eb8d777d4d4980b20526f752db59f86b9d18 Mon Sep 17 00:00:00 2001 From: Chris H-C Date: Wed, 26 Jun 2024 16:09:01 -0400 Subject: [PATCH 47/51] bug 1657947 - Document new labeled_{custom|memory|timing}_distributions --- .dictionary | 6 +- docs/user/SUMMARY.md | 3 + .../reference/metrics/custom_distribution.md | 2 +- .../metrics/labeled_custom_distributions.md | 206 +++++++++++ .../metrics/labeled_memory_distributions.md | 165 +++++++++ .../metrics/labeled_timing_distributions.md | 331 ++++++++++++++++++ 6 files changed, 711 insertions(+), 2 deletions(-) create mode 100644 docs/user/reference/metrics/labeled_custom_distributions.md create mode 100644 docs/user/reference/metrics/labeled_memory_distributions.md create mode 100644 docs/user/reference/metrics/labeled_timing_distributions.md diff --git a/.dictionary b/.dictionary index a749c32240..baafec85af 100644 --- a/.dictionary +++ b/.dictionary @@ -1,4 +1,4 @@ -personal_ws-1.1 en 284 utf-8 +personal_ws-1.1 en 289 utf-8 AAR AARs ABI @@ -108,6 +108,7 @@ XCFramework Xcode YAML aarch +addon alphanumerics analytics anonymized @@ -128,6 +129,7 @@ booleans brizental bugfixes camelCase +cancelled carthage changelog chutten @@ -148,6 +150,7 @@ datetime destructor deterministically dev +devtools dexter distributable docstrings @@ -225,6 +228,7 @@ preinit py pytest rethrow +retransmission rfloor rkv rkv's diff --git a/docs/user/SUMMARY.md b/docs/user/SUMMARY.md index baa9e932ae..101433b907 100644 --- a/docs/user/SUMMARY.md +++ b/docs/user/SUMMARY.md @@ -70,12 +70,15 @@ - [String List](reference/metrics/string_list.md) - [Timespan](reference/metrics/timespan.md) - [Timing Distribution](reference/metrics/timing_distribution.md) + - [Labeled Timing Distributions](reference/metrics/labeled_timing_distributions.md) - [Memory Distribution](reference/metrics/memory_distribution.md) + - [Labeled Memory Distributions](reference/metrics/labeled_memory_distributions.md) - [UUID](reference/metrics/uuid.md) - [URL](reference/metrics/url.md) - [Datetime](reference/metrics/datetime.md) - [Event](reference/metrics/event.md) - [Custom Distribution](reference/metrics/custom_distribution.md) + - [Labeled Custom Distributions](reference/metrics/labeled_custom_distributions.md) - [Quantity](reference/metrics/quantity.md) - [Rate](reference/metrics/rate.md) - [Text](reference/metrics/text.md) diff --git a/docs/user/reference/metrics/custom_distribution.md b/docs/user/reference/metrics/custom_distribution.md index 10ee501e06..fd1ad29cae 100644 --- a/docs/user/reference/metrics/custom_distribution.md +++ b/docs/user/reference/metrics/custom_distribution.md @@ -145,7 +145,7 @@ This API is not currently exposed in Firefox Desktop, see [Bug 1884183](https:// ### `testGetValue` -Gets the recorded value for a given counter metric. +Gets the recorded value for a given custom distribution metric. Returns a struct with counts per buckets and total sum if data is stored. Returns a language-specific empty/null value if no data is stored. Has an optional argument to specify the name of the ping you wish to retrieve data from, except diff --git a/docs/user/reference/metrics/labeled_custom_distributions.md b/docs/user/reference/metrics/labeled_custom_distributions.md new file mode 100644 index 0000000000..a97e5a324d --- /dev/null +++ b/docs/user/reference/metrics/labeled_custom_distributions.md @@ -0,0 +1,206 @@ +# Labeled Custom Distributions + +Labeled custom distributions are used to record different related distributions of arbitrary values. + +If your data is timing or memory based and you don't need direct control over histogram buckets, +consider instead: + +* [Labeled Timing Distributions](labeled_timing_distributions.md) +* [Labeled Memory Distributions](labeled_memory_distributions.md) + +## Recording API + +### `accumulateSamples` + +Accumulate the provided samples in the metric. + +{{#include ../../../shared/tab_header.md}} + +
    +
    +
    +
    +
    + +```Rust +use glean_metrics::network; + +network::http3_late_ack_ratio + .get("ack") + .accumulateSamples(vec![(stats.late_ack * 10000) / stats.packets_tx]); +network::http3_late_ack_ratio + .get("pto") + .accumulateSamples(vec![(stats.pto_ack * 10000) / stats.packets_tx]); +``` + +
    +
    +
    + +{{#include ../../../shared/tab_footer.md}} + +#### Recorded Errors + +* [`invalid_value`](../../user/metrics/error-reporting.md): if recording any negative samples +{{#include ../../_includes/label-errors.md}} + +### `accumulateSingleSample` + +Accumulates one sample and appends it to the metric. + +{{#include ../../../shared/tab_header.md}} + +
    +
    +
    +
    +
    + +```Rust +use glean_metrics::network; + +network::http3_late_ack_ratio + .get("ack") + .accumulateSingleSample((stats.late_ack * 10000) / stats.packets_tx); +network::http3_late_ack_ratio + .get("pto") + .accumulateSingleSample((stats.pto_ack * 10000) / stats.packets_tx); +``` + +
    +
    +
    + +{{#include ../../../shared/tab_footer.md}} + +#### Recorded Errors + +* [`invalid_value`](../../user/metrics/error-reporting.md): if recording a negative sample +{{#include ../../_includes/label-errors.md}} + +## Testing API + +### `testGetValue` + +Gets the recorded value for a given label in a labeled custom distribution metric. +Returns a struct with counts per buckets and total sum if data is stored. +Returns a language-specific empty/null value if no data is stored. +Has an optional argument to specify the name of the ping you wish to retrieve data from, except +in Rust where it's required. `None` or no argument will default to the first value found for `send_in_pings`. + +{{#include ../../../shared/tab_header.md}} + +
    +
    +
    +
    +
    + +```Rust +use glean_metrics::network; + +// Assert the sum of all samples is 42. +assert_eq!(42, network::http3_late_ack_ratio.get("ack").test_get_value(None).unwrap().sum); + +// Assert there's only the one sample +assert_eq!(1, network::http3_late_ack_ratio.get("ack").test_get_value(None).unwrap().count); + +// Buckets are indexed by their lower bound. +assert_eq!(1, etwork::http3_late_ack_ratio.get("ack").test_get_value(None).unwrap().values[41]); +``` + +
    +
    +
    + +{{#include ../../../shared/tab_footer.md}} + +### `testGetNumRecordedErrors` + +Gets the number of errors recorded for a given labeled custom distribution metric in total. + +{{#include ../../../shared/tab_header.md}} + +
    +
    +
    +
    +
    + +```Rust +use glean::ErrorType; +use glean_metrics::network; + +// Assert there were no negative values instrumented. +assert_eq!( + 0, + network::http3_late_ack_ratio.test_get_num_recorded_errors( + ErrorType::InvalidValue, + None + ) +); +``` + +
    +
    +
    + +{{#include ../../../shared/tab_footer.md}} + +## Metric parameters + +Example labeled custom distribution metric definition: + +```YAML +network: + http3_late_ack_ratio: + type: labeled_custom_distribution + description: > + HTTP3: The ratio of spurious retransmissions per packets sent, + represented as an integer permdecimille: + `(spurious_retransmission / packet sent * 10000)` + range_min: 1 + range_max: 2000 + bucket_count 100 + histogram_type: exponential + bugs: + - https://bugzilla.mozilla.org/000000 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=000000#c3 + notification_emails: + - me@mozilla.com + expires: 175 + labels: + - ack + - pto +``` + +### Extra metric parameters + +#### `range_min`, `range_max`, `bucket_count`, and `histogram_type` (Required) + +Labeled custom distributions have the following required parameters: + +- `range_min`: (Integer) The minimum value of the first bucket +- `range_max`: (Integer) The minimum value of the last bucket +- `bucket_count`: (Integer) The number of buckets +- `histogram_type`: + - `linear`: The buckets are evenly spaced + - `exponential`: The buckets follow a natural logarithmic distribution + +{{#include ../../_includes/labels-parameter.md}} + +## Data questions + +* What is the distribution of retransmission ratios per connection? +* What is the distribution of how long it takes to load extensions' content scripts, by addon id? + +## Limits + +* The maximum value of `bucket_count` is 100. +* Only non-negative integer values may be recorded (`>=0`). +{{#include ../../_includes/label-limits.md}} + +## Reference + +* Rust API docs: [`LabeledMetric`](../../../docs/glean/private/struct.LabeledMetric.html), [`CustomDistributionMetricType`](../../../docs/glean/private/struct.CustomDistributionMetric.html) diff --git a/docs/user/reference/metrics/labeled_memory_distributions.md b/docs/user/reference/metrics/labeled_memory_distributions.md new file mode 100644 index 0000000000..da291b6f37 --- /dev/null +++ b/docs/user/reference/metrics/labeled_memory_distributions.md @@ -0,0 +1,165 @@ +# Labeled Memory Distributions + +Labeled memory distributions are used to record different related distributions of memory sizes. + +See [the Memory Distribution reference](memory_distribution.md) for details on bucket distribution, +and a histogram simulator. + +## Recording API + +### `accumulate` + +Accumulate the provided sample in the metric. + +{{#include ../../../shared/tab_header.md}} + +
    +
    +
    +
    +
    + +```Rust +use glean_metrics::network; + +network::http_upload_bandwidth + .get(http_version) + .accumulate(self.request_size * 8.0 / 1048576.0 / send_time.as_secs()); +``` + +
    +
    +
    + +{{#include ../../../shared/tab_footer.md}} + +#### Recorded Errors + +* [`invalid_value`](../../user/metrics/error-reporting.md): if recording a memory size that is negative or over 1 TB. +{{#include ../../_includes/label-errors.md}} + +## Testing API + +### `testGetValue` + +Gets the recorded value for a given label in a labeled memory distribution metric. +Returns a struct with counts per buckets and total sum if data is stored. +Returns a language-specific empty/null value if no data is stored. +Has an optional argument to specify the name of the ping you wish to retrieve data from, except +in Rust where it's required. `None` or no argument will default to the first value found for `send_in_pings`. + +{{#include ../../../shared/tab_header.md}} + +
    +
    +
    +
    +
    + +```Rust +use glean_metrics::network; + +// Assert the sum of all HTTP2 samples is 42MBps. +assert_eq!(42, network::http_upload_bandwidth.get("h2").test_get_value(None).unwrap().sum); + +// Assert there's only the one sample +assert_eq!(1, network::http_upload_badwidth.get("h2").test_get_value(None).unwrap().count); + +// Buckets are indexed by their lower bound. +assert_eq!(1, network::http_upload_bandwidth.get("h2").test_get_value(None).unwrap().values[41]); +``` + +
    +
    +
    + +{{#include ../../../shared/tab_footer.md}} + +### `testGetNumRecordedErrors` + +Gets the number of errors recorded for a given labeled custom distribution metric in total. + +{{#include ../../../shared/tab_header.md}} + +
    +
    +
    +
    +
    + +```Rust +use glean::ErrorType; +use glean_metrics::network; + +// Assert there were no negative or overlarge values instrumented. +assert_eq!( + 0, + network::http_upload_bandwidth.test_get_num_recorded_errors( + ErrorType::InvalidValue, + None + ) +); +``` + +
    +
    +
    + +{{#include ../../../shared/tab_footer.md}} + +## Metric parameters + +Example labeled memory distribution metric definition: + +```YAML +network: + http_upload_bandwidth: + type: labeled_memory_distribution + description: > + The upload bandwidth for requests larger than 10MB, + per HTTP protocol version. + memory_unit: megabyte + bugs: + - https://bugzilla.mozilla.org/000000 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=000000#c3 + notification_emails: + - me@mozilla.com + expires: 175 + labels: + - h3 + - h2 + - http/1.0 + - http/1.1 +``` + +### Extra metric parameters + +#### `memory_unit` + +Memory distributions have an optional `memory_unit` parameter, +which specifies the unit the incoming memory size values are recorded in. + +The allowed values for `memory_unit` are: + +* `byte` (default) +* `kilobyte` (`= 2^10 = 1,024 bytes`) +* `megabyte` (`= 2^20 = 1,048,576 bytes`) +* `gigabyte` (`= 2^30 = 1,073,741,824 bytes`) + +{{#include ../../_includes/labels-parameter.md}} + +## Data questions + +* What is the distribution of upload bandwidth rates per HTTP protocol version? +* What is the distribution of bytes received per DOM network API? + +## Limits + +* The maximum memory size that can be recorded is 1 Terabyte (240 bytes). + Larger sizes will be truncated to 1 Terabyte. +{{#include ../../_includes/label-limits.md}} + +## Reference + +* Rust API docs: [`LabeledMetric`](../../../docs/glean/private/struct.LabeledMetric.html), [`MemoryDistributionMetricType`](../../../docs/glean/private/struct.MemoryDistributionMetric.html) diff --git a/docs/user/reference/metrics/labeled_timing_distributions.md b/docs/user/reference/metrics/labeled_timing_distributions.md new file mode 100644 index 0000000000..9dcafa546a --- /dev/null +++ b/docs/user/reference/metrics/labeled_timing_distributions.md @@ -0,0 +1,331 @@ +# Labeled Timing Distributions + +Labeled timing distributions are used to record different related distributions of time measurements. + +See [the Timing Distribution reference](timing_distribution.md) for details on bucket distribution, +specifics about how Glean records time, and a histogram simulator. + +## Recording API + +### `start` + +Start tracking time for the provided metric for the given label. +Multiple timers for multiple labels can run simultaneously. + +Returns a unique `TimerId` for the new timer. + +{{#include ../../../shared/tab_header.md}} + +
    +
    +
    +
    +
    + +```Rust +use glean_metrics::devtools; + +self.start = devtools::cold_toolbox_open_delay + .get(toolbox_id) + .start(); +``` + +
    +
    +
    + +{{#include ../../../shared/tab_footer.md}} + +#### Recorded Errors + +{{#include ../../_includes/label-errors.md}} + +### `stopAndAccumulate` + +Stops tracking time for the provided timer from the metric for the given label. + +Adds a count to the corresponding bucket in the label's timing distribution. + +Do not use the provided `TimerId` after passing it to this method. + +{{#include ../../../shared/tab_header.md}} + +
    +
    +
    +
    +
    + +```Rust +use glean_metrics::devtools; + +devtools::cold_toolbox_open_delay + .get(toolbox_id) + .stop_and_accumulate(self.start); +``` + +
    +
    +
    + +{{#include ../../../shared/tab_footer.md}} + +#### Recorded errors + +* [`invalid_state`](../../user/metrics/error-reporting.md): If a non-existing, cancelled, or already-stopped timer is stopped again. +{{#include ../../_includes/label-errors.md}} + +### `cancel` + +Aborts a previous `start` call, consuming the supplied timer id. + +{{#include ../../../shared/tab_header.md}} + +
    +
    +
    +
    +
    + +```Rust +use glean_metrics::devtools; + +devtools::cold_toolbox_open_delay + .get(toolbox_id) + .cancel(self.start); +``` + +
    +
    +
    + +{{#include ../../../shared/tab_footer.md}} + +#### Recorded errors + +{{#include ../../_includes/label-errors.md}} + +### `accumulateSamples` + +Accumulates the provided, signed samples in the metric for a given label. +Where possible, have Glean do the timing for you and don't use methods like this one. +If you are doing timing yourself, +ensure your time source is monotonic and behaves consistently across platforms. + +This is required so that the platform-specific code can provide us with +64 bit signed integers if no `u64` comparable type is available. This +will take care of filtering and reporting errors for any provided negative +sample. + +Please note that this assumes that the provided samples are already in +the "unit" declared by the instance of the metric type (e.g. if the +instance this method was called on is using `TimeUnit::Second`, then +`samples` are assumed to be in that unit). + +{{#include ../../../shared/tab_header.md}} + +
    +
    +
    +
    +
    + +```Rust +use glean_metrics::devtools; + +devtools::cold_toolbox_open_delay + .get(toolbox_id) + .accumulate_samples(samples); +``` + +
    +
    +
    + +{{#include ../../../shared/tab_footer.md}} + +#### Recorded errors + +* [`invalid_value`](../../user/metrics/error-reporting.md): If recording a negative sample. +* [`invalid_overflow`](../../user/metrics/error-reporting.md): If recording a sample longer than the maximum for the given `time_unit`. +{{#include ../../_includes/label-errors.md}} + +### `accumulateSingleSample` + +Accumulates a single signed sample and appends it to the metric for the provided label. +Prefer `start()` and `stopAndAccumulate()` where possible, +but if you must record time externally please prefer this method for individual samples +(avoids having to allocate and pass collections). + +A signed value is required so that the platform-specific code can provide +us with a 64 bit signed integer if no `u64` comparable type is available. +This will take care of filtering and reporting errors for a negative +sample. + +Please note that this assumes that the provided sample is already in +the "unit" declared by the instance of the metric type (e.g. if the +instance this method was called on is using `TimeUnit::Second`, then +`sample` is assumed to be in that unit). + +{{#include ../../../shared/tab_header.md}} + +
    +
    +
    +
    +
    + +```Rust +use glean_metrics::devtools; + +devtools::cold_toolbox_open_delay + .get(toolbox_id) + .accumulate_single_sample(sample); +``` + +
    +
    +
    + +{{#include ../../../shared/tab_footer.md}} + +#### Recorded errors + +* [`invalid_value`](../../user/metrics/error-reporting.md): If recording a negative sample. +* [`invalid_overflow`](../../user/metrics/error-reporting.md): If recording a sample longer than the maximum for the given `time_unit`. +{{#include ../../_includes/label-errors.md}} + + +## Testing API + +### `testGetValue` + +Gets the recorded value for a given label in a labeled timing distribution metric. +Returns a struct with counts per buckets and total sum if data is stored. +Returns a language-specific empty/null value if no data is stored. +Has an optional argument to specify the name of the ping you wish to retrieve data from, except +in Rust where it's required. `None` or no argument will default to the first value found for `send_in_pings`. + +{{#include ../../../shared/tab_header.md}} + +
    +
    +
    +
    +
    + +```Rust +use glean_metrics::devtools; + +// Get the current snapshot of stored values. +let snapshot = devtools::cold_toolbox_open_delay.get("webconsole").test_get_value(None).unwrap(); + +// Usually you don't know the exact timing values, +// but you do know how many samples there are: +assert_eq!(2, snapshot.count); +// ...and the lower bound of how long they all took: +assert_ge!(400, snapshot.sum); +``` + +
    +
    +
    + +{{#include ../../../shared/tab_footer.md}} + +### `testGetNumRecordedErrors` + +Gets the number of errors recorded for a given labeled timing distribution metric in total. + +{{#include ../../../shared/tab_header.md}} + +
    +
    +
    +
    +
    + +```Rust +use glean::ErrorType; +use glean_metrics::network; + +// Assert there were no negative values instrumented. +assert_eq!( + 0, + devtools::cold_toolbox_open_delay.test_get_num_recorded_errors( + ErrorType::InvalidValue, + None + ) +); +``` + +
    +
    +
    + +{{#include ../../../shared/tab_footer.md}} + +## Metric parameters + +Example labeled timing distribution metric definition: + +```YAML +devtools: + cold_toolbox_open_delay: + type: labeled_timing_distribution + description: > + Time taken to open the first DevTools toolbox, per tool being opened. + time_unit: millisecond + bugs: + - https://bugzilla.mozilla.org/000000 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=000000#c3 + notification_emails: + - me@mozilla.com + expires: 175 + labels: + - inspector + - webconsole + - jsdebugger + ... +``` + +### Extra metric parameters + +#### `time_unit` + +Labeled timing distributions have an optional `time_unit` +parameter to specify the smallest unit of resolution that it will record. +The allowed values for `time_unit` are: + +* `nanosecond` (default) +* `microsecond` +* `millisecond` +* `second` +* `minute` +* `hour` +* `day` + +{{#include ../../_includes/labels-parameter.md}} + +## Data questions + +* What is the distribution of initial load times of devtools toolboxes, per tool? +* What is the distribution of how long it takes to load extensions' content scripts, by addon id? + +## Limits + +* Timings are recorded in nanoseconds + * In Rust, [`time::precise_time_ns()`](https://docs.rs/time/0.1.42/time/fn.precise_time_ns.html) is used. +* The maximum timing value that will be recorded depends on the `time_unit` parameter: + + - `nanosecond`: 1ns <= x <= 10 minutes + - `microsecond`: 1μs <= x <= ~6.94 days + - `millisecond`: 1ms <= x <= ~19 years + + Longer times will be truncated to the maximum value and an error will be recorded. +{{#include ../../_includes/label-limits.md}} + +## Reference + +* Rust API docs: [`LabeledMetric`](../../../docs/glean/private/struct.LabeledMetric.html), [`TimingDistributionMetricType`](../../../docs/glean/private/struct.TimingDistributionMetric.html) From fd68d93277904e0a26970b4f5d6748399789e149 Mon Sep 17 00:00:00 2001 From: Chris H-C Date: Fri, 12 Jul 2024 15:20:56 -0400 Subject: [PATCH 48/51] bug 1657947 - Update glean_parser version to support labeled_{custom|memory|timing}_distributions --- Cargo.lock | 2 +- Makefile | 2 +- glean-core/Cargo.toml | 2 +- glean-core/build/Cargo.toml | 2 +- glean-core/build/src/lib.rs | 2 +- glean-core/ios/sdk_generator.sh | 2 +- .../telemetry/glean-gradle-plugin/GleanGradlePlugin.groovy | 2 +- pyproject.toml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fe2c3c3137..71e8bc98f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -336,7 +336,7 @@ dependencies = [ [[package]] name = "glean-build" -version = "14.0.1" +version = "14.2.0" dependencies = [ "tempfile", "xshell-venv", diff --git a/Makefile b/Makefile index 7fbf2cb4d7..f6bb70fd35 100644 --- a/Makefile +++ b/Makefile @@ -151,7 +151,7 @@ docs-python: build-python ## Build the Python documentation .PHONY: docs docs-rust docs-swift docs-metrics: setup-python ## Build the internal metrics documentation - $(GLEAN_PYENV)/bin/pip install glean_parser~=14.0 + $(GLEAN_PYENV)/bin/pip install glean_parser~=14.2 $(GLEAN_PYENV)/bin/glean_parser translate --allow-reserved \ -f markdown \ -o ./docs/user/user/collected-metrics \ diff --git a/glean-core/Cargo.toml b/glean-core/Cargo.toml index ffcf7faff3..f98d5f5c37 100644 --- a/glean-core/Cargo.toml +++ b/glean-core/Cargo.toml @@ -21,7 +21,7 @@ include = [ rust-version = "1.66" [package.metadata.glean] -glean-parser = "14.0.1" +glean-parser = "14.2.0" [badges] circle-ci = { repository = "mozilla/glean", branch = "main" } diff --git a/glean-core/build/Cargo.toml b/glean-core/build/Cargo.toml index 1b4b9796d4..5a09c7c0fe 100644 --- a/glean-core/build/Cargo.toml +++ b/glean-core/build/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "glean-build" -version = "14.0.1" +version = "14.2.0" edition = "2021" description = "Glean SDK Rust build helper" repository = "https://github.com/mozilla/glean" diff --git a/glean-core/build/src/lib.rs b/glean-core/build/src/lib.rs index 0b3fc17f5e..97feb555f8 100644 --- a/glean-core/build/src/lib.rs +++ b/glean-core/build/src/lib.rs @@ -39,7 +39,7 @@ use std::env; use xshell_venv::{Result, Shell, VirtualEnv}; -const GLEAN_PARSER_VERSION: &str = "14.0.1"; +const GLEAN_PARSER_VERSION: &str = "14.2.0"; /// A Glean Rust bindings generator. pub struct Builder { diff --git a/glean-core/ios/sdk_generator.sh b/glean-core/ios/sdk_generator.sh index 15b2f194e0..5a6b875f1d 100755 --- a/glean-core/ios/sdk_generator.sh +++ b/glean-core/ios/sdk_generator.sh @@ -25,7 +25,7 @@ set -e -GLEAN_PARSER_VERSION=14.0 +GLEAN_PARSER_VERSION=14.2 # CMDNAME is used in the usage text below. # shellcheck disable=SC2034 diff --git a/gradle-plugin/src/main/groovy/mozilla/telemetry/glean-gradle-plugin/GleanGradlePlugin.groovy b/gradle-plugin/src/main/groovy/mozilla/telemetry/glean-gradle-plugin/GleanGradlePlugin.groovy index 70943ea667..8991acb31d 100644 --- a/gradle-plugin/src/main/groovy/mozilla/telemetry/glean-gradle-plugin/GleanGradlePlugin.groovy +++ b/gradle-plugin/src/main/groovy/mozilla/telemetry/glean-gradle-plugin/GleanGradlePlugin.groovy @@ -50,7 +50,7 @@ abstract class GleanMetricsYamlTransform implements TransformAction { // The version of glean_parser to install from PyPI. - private String GLEAN_PARSER_VERSION = "14.0" + private String GLEAN_PARSER_VERSION = "14.2" // The version of Miniconda is explicitly specified. // Miniconda3-4.5.12 is known to not work on Windows. private String MINICONDA_VERSION = "24.3.0-0" diff --git a/pyproject.toml b/pyproject.toml index 0edcf4df7c..b4a52ddcc3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,7 @@ maintainers = [ dependencies = [ "semver>=2.13.0", - "glean_parser~=14.0", + "glean_parser~=14.2", ] [project.urls] From ad9855581e06c0190eabd1c2ebe06ca2eeefaa9a Mon Sep 17 00:00:00 2001 From: Jan-Erik Rediger Date: Thu, 18 Jul 2024 17:05:17 +0200 Subject: [PATCH 49/51] Swift: Use String(decoding:as:) to directly decode the UTF8 bytes This is otherwise a `non_optional_string_data_conversion` swiftlint warning. --- glean-core/ios/Glean/Metrics/ObjectMetric.swift | 2 +- glean-core/ios/GleanTests/TestUtils.swift | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/glean-core/ios/Glean/Metrics/ObjectMetric.swift b/glean-core/ios/Glean/Metrics/ObjectMetric.swift index 841f488a79..103e7b9466 100644 --- a/glean-core/ios/Glean/Metrics/ObjectMetric.swift +++ b/glean-core/ios/Glean/Metrics/ObjectMetric.swift @@ -13,7 +13,7 @@ extension Array: ObjectSerialize where Element: Codable { public func intoSerializedObject() -> String { let jsonEncoder = JSONEncoder() let jsonData = try! jsonEncoder.encode(self) - let json = String(data: jsonData, encoding: String.Encoding.utf8)! + let json = String(decoding: jsonData, as: UTF8.self) return json } } diff --git a/glean-core/ios/GleanTests/TestUtils.swift b/glean-core/ios/GleanTests/TestUtils.swift index 9568363a7b..ead1f6fbb1 100644 --- a/glean-core/ios/GleanTests/TestUtils.swift +++ b/glean-core/ios/GleanTests/TestUtils.swift @@ -104,9 +104,7 @@ func tearDownStubs() { func JSONStringify(_ json: Any) -> String { do { let data = try JSONSerialization.data(withJSONObject: json, options: .prettyPrinted) - if let string = String(data: data, encoding: String.Encoding.utf8) { - return string - } + return String(decoding: data, as: UTF8.self) } catch { print(error) } From 1ae14f5718c402ab5c4bc1993a5e985947564da2 Mon Sep 17 00:00:00 2001 From: Chris H-C Date: Mon, 22 Jul 2024 11:49:23 -0400 Subject: [PATCH 50/51] bug 1909244 - Update glean_parser to fix rust codegen Add labeled metrics to Rust sample app to test codegen. --- CHANGELOG.md | 1 + Cargo.lock | 2 +- Makefile | 2 +- glean-core/Cargo.toml | 2 +- glean-core/build/Cargo.toml | 2 +- glean-core/build/src/lib.rs | 2 +- glean-core/ios/sdk_generator.sh | 2 +- glean-core/python/glean/__init__.py | 2 +- .../GleanGradlePlugin.groovy | 2 +- pyproject.toml | 2 +- samples/rust/metrics.yaml | 43 +++++++++++++++++++ 11 files changed, 53 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be5ba86905..df2092ce12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * General * Bump the string length limit to 255 characters ([#2857](https://github.com/mozilla/glean/pull/2857)) * New metric `glean.database.write_time` to measure database writes ([#2845](https://github.com/mozilla/glean/pull/2845)) + * Require glean_parser v14.3.0 ([bug 1909244](https://bugzilla.mozilla.org/show_bug.cgi?id=1909244)) * Android * Delay log init until Glean is getting initialized ([#2858](https://github.com/mozilla/glean/pull/2858)) * Update to Gradle v8.8 ([#2860](https://github.com/mozilla/glean/pull/2860)) diff --git a/Cargo.lock b/Cargo.lock index 71e8bc98f3..ca25c9c27d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -336,7 +336,7 @@ dependencies = [ [[package]] name = "glean-build" -version = "14.2.0" +version = "14.3.0" dependencies = [ "tempfile", "xshell-venv", diff --git a/Makefile b/Makefile index f6bb70fd35..c135e355e6 100644 --- a/Makefile +++ b/Makefile @@ -151,7 +151,7 @@ docs-python: build-python ## Build the Python documentation .PHONY: docs docs-rust docs-swift docs-metrics: setup-python ## Build the internal metrics documentation - $(GLEAN_PYENV)/bin/pip install glean_parser~=14.2 + $(GLEAN_PYENV)/bin/pip install glean_parser~=14.3 $(GLEAN_PYENV)/bin/glean_parser translate --allow-reserved \ -f markdown \ -o ./docs/user/user/collected-metrics \ diff --git a/glean-core/Cargo.toml b/glean-core/Cargo.toml index f98d5f5c37..48f42cabe6 100644 --- a/glean-core/Cargo.toml +++ b/glean-core/Cargo.toml @@ -21,7 +21,7 @@ include = [ rust-version = "1.66" [package.metadata.glean] -glean-parser = "14.2.0" +glean-parser = "14.3.0" [badges] circle-ci = { repository = "mozilla/glean", branch = "main" } diff --git a/glean-core/build/Cargo.toml b/glean-core/build/Cargo.toml index 5a09c7c0fe..6089b0d00c 100644 --- a/glean-core/build/Cargo.toml +++ b/glean-core/build/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "glean-build" -version = "14.2.0" +version = "14.3.0" edition = "2021" description = "Glean SDK Rust build helper" repository = "https://github.com/mozilla/glean" diff --git a/glean-core/build/src/lib.rs b/glean-core/build/src/lib.rs index 97feb555f8..23f6841135 100644 --- a/glean-core/build/src/lib.rs +++ b/glean-core/build/src/lib.rs @@ -39,7 +39,7 @@ use std::env; use xshell_venv::{Result, Shell, VirtualEnv}; -const GLEAN_PARSER_VERSION: &str = "14.2.0"; +const GLEAN_PARSER_VERSION: &str = "14.3.0"; /// A Glean Rust bindings generator. pub struct Builder { diff --git a/glean-core/ios/sdk_generator.sh b/glean-core/ios/sdk_generator.sh index 5a6b875f1d..9df4877759 100755 --- a/glean-core/ios/sdk_generator.sh +++ b/glean-core/ios/sdk_generator.sh @@ -25,7 +25,7 @@ set -e -GLEAN_PARSER_VERSION=14.2 +GLEAN_PARSER_VERSION=14.3 # CMDNAME is used in the usage text below. # shellcheck disable=SC2034 diff --git a/glean-core/python/glean/__init__.py b/glean-core/python/glean/__init__.py index b7c961675f..4a34816964 100644 --- a/glean-core/python/glean/__init__.py +++ b/glean-core/python/glean/__init__.py @@ -30,7 +30,7 @@ __email__ = "glean-team@mozilla.com" -GLEAN_PARSER_VERSION = "14.0.1" +GLEAN_PARSER_VERSION = "14.3.0" parser_version = VersionInfo.parse(GLEAN_PARSER_VERSION) parser_version_next_major = parser_version.bump_major() diff --git a/gradle-plugin/src/main/groovy/mozilla/telemetry/glean-gradle-plugin/GleanGradlePlugin.groovy b/gradle-plugin/src/main/groovy/mozilla/telemetry/glean-gradle-plugin/GleanGradlePlugin.groovy index 8991acb31d..85859aa000 100644 --- a/gradle-plugin/src/main/groovy/mozilla/telemetry/glean-gradle-plugin/GleanGradlePlugin.groovy +++ b/gradle-plugin/src/main/groovy/mozilla/telemetry/glean-gradle-plugin/GleanGradlePlugin.groovy @@ -50,7 +50,7 @@ abstract class GleanMetricsYamlTransform implements TransformAction { // The version of glean_parser to install from PyPI. - private String GLEAN_PARSER_VERSION = "14.2" + private String GLEAN_PARSER_VERSION = "14.3" // The version of Miniconda is explicitly specified. // Miniconda3-4.5.12 is known to not work on Windows. private String MINICONDA_VERSION = "24.3.0-0" diff --git a/pyproject.toml b/pyproject.toml index b4a52ddcc3..03701313c5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,7 @@ maintainers = [ dependencies = [ "semver>=2.13.0", - "glean_parser~=14.2", + "glean_parser~=14.3", ] [project.urls] diff --git a/samples/rust/metrics.yaml b/samples/rust/metrics.yaml index f1cbf928ea..367a437c84 100644 --- a/samples/rust/metrics.yaml +++ b/samples/rust/metrics.yaml @@ -25,6 +25,49 @@ test.metrics: send_in_pings: - prototype + sample_labeled_counter: &defaults + type: labeled_counter + description: | + Just testing labeled_counter. + bugs: + - https://bugzilla.mozilla.org/1907991 + data_reviews: + - N/A + notification_emails: + - nobody@example.com + expires: never + send_in_pings: + - prototype + no_lint: + - COMMON_PREFIX + + sample_labeled_custom_distribution: + <<: *defaults + type: labeled_custom_distribution + range_min: 0 + range_max: 100 + bucket_count: 10 + histogram_type: linear + labels: + - aLabel + - 2label + + sample_labeled_memory_distribution: + <<: *defaults + type: labeled_memory_distribution + memory_unit: kilobyte + labels: + - aLabel + - 2label + + sample_labeled_timing_distribution: + <<: *defaults + type: labeled_timing_distribution + time_unit: millisecond + labels: + - aLabel + - 2label + party: balloons: type: object From 09ae5472151c47a4fb807caa9c7a51bc2b5dc398 Mon Sep 17 00:00:00 2001 From: Chris H-C Date: Tue, 23 Jul 2024 09:31:11 -0400 Subject: [PATCH 51/51] Bumped version to 60.4.0 --- .buildconfig.yml | 2 +- CHANGELOG.md | 6 +++++- Cargo.lock | 4 ++-- DEPENDENCIES.md | 6 +++--- glean-core/Cargo.toml | 2 +- glean-core/rlb/Cargo.toml | 4 ++-- .../telemetry/glean-gradle-plugin/GleanGradlePlugin.groovy | 2 +- pyproject.toml | 2 +- 8 files changed, 16 insertions(+), 12 deletions(-) diff --git a/.buildconfig.yml b/.buildconfig.yml index 56ff8a8388..201199aed9 100644 --- a/.buildconfig.yml +++ b/.buildconfig.yml @@ -1,4 +1,4 @@ -libraryVersion: 60.3.0 +libraryVersion: 60.4.0 groupId: org.mozilla.telemetry projects: glean: diff --git a/CHANGELOG.md b/CHANGELOG.md index df2092ce12..6145aeffb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Unreleased changes -[Full changelog](https://github.com/mozilla/glean/compare/v60.3.0...main) +[Full changelog](https://github.com/mozilla/glean/compare/v60.4.0...main) + +# v60.4.0 (2024-07-23) + +[Full changelog](https://github.com/mozilla/glean/compare/v60.3.0...v60.4.0) * General * Bump the string length limit to 255 characters ([#2857](https://github.com/mozilla/glean/pull/2857)) diff --git a/Cargo.lock b/Cargo.lock index ca25c9c27d..ec9b6f2a06 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -318,7 +318,7 @@ dependencies = [ [[package]] name = "glean" -version = "60.3.0" +version = "60.4.0" dependencies = [ "crossbeam-channel", "env_logger", @@ -359,7 +359,7 @@ dependencies = [ [[package]] name = "glean-core" -version = "60.3.0" +version = "60.4.0" dependencies = [ "android_logger", "bincode", diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md index d34ac131db..e35ea224d8 100644 --- a/DEPENDENCIES.md +++ b/DEPENDENCIES.md @@ -4951,9 +4951,9 @@ SOFTWARE. The following text applies to code linked from these dependencies: -* [glean-core 60.3.0]( https://github.com/mozilla/glean ) -* [glean-build 14.0.1]( https://github.com/mozilla/glean ) -* [glean 60.3.0]( https://github.com/mozilla/glean ) +* [glean-core 60.4.0]( https://github.com/mozilla/glean ) +* [glean-build 14.3.0]( https://github.com/mozilla/glean ) +* [glean 60.4.0]( https://github.com/mozilla/glean ) * [zeitstempel 0.1.1]( https://github.com/badboy/zeitstempel ) ``` diff --git a/glean-core/Cargo.toml b/glean-core/Cargo.toml index 48f42cabe6..0e631d7998 100644 --- a/glean-core/Cargo.toml +++ b/glean-core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "glean-core" -version = "60.3.0" +version = "60.4.0" authors = ["Jan-Erik Rediger ", "The Glean Team "] description = "A modern Telemetry library" repository = "https://github.com/mozilla/glean" diff --git a/glean-core/rlb/Cargo.toml b/glean-core/rlb/Cargo.toml index c1d4ea9c1f..42ba287a2b 100644 --- a/glean-core/rlb/Cargo.toml +++ b/glean-core/rlb/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "glean" -version = "60.3.0" +version = "60.4.0" authors = ["Jan-Erik Rediger ", "The Glean Team "] description = "Glean SDK Rust language bindings" repository = "https://github.com/mozilla/glean" @@ -23,7 +23,7 @@ maintenance = { status = "actively-developed" } [dependencies.glean-core] path = ".." -version = "60.3.0" +version = "60.4.0" [dependencies] inherent = "1" diff --git a/gradle-plugin/src/main/groovy/mozilla/telemetry/glean-gradle-plugin/GleanGradlePlugin.groovy b/gradle-plugin/src/main/groovy/mozilla/telemetry/glean-gradle-plugin/GleanGradlePlugin.groovy index 85859aa000..d43f7c6a69 100644 --- a/gradle-plugin/src/main/groovy/mozilla/telemetry/glean-gradle-plugin/GleanGradlePlugin.groovy +++ b/gradle-plugin/src/main/groovy/mozilla/telemetry/glean-gradle-plugin/GleanGradlePlugin.groovy @@ -552,7 +552,7 @@ except: void apply(Project project) { isOffline = project.gradle.startParameter.offline - project.ext.glean_version = "60.3.0" + project.ext.glean_version = "60.4.0" def parserVersion = gleanParserVersion(project) // Print the required glean_parser version to the console. This is diff --git a/pyproject.toml b/pyproject.toml index 03701313c5..48c0414f29 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "glean-sdk" -version = "60.3.0" +version = "60.4.0" requires-python = ">=3.8" classifiers = [ "Intended Audience :: Developers",