Loading components/service/glean/build.gradle +1 −1 Original line number Diff line number Diff line Loading @@ -14,7 +14,7 @@ apply plugin: 'kotlin-android' * created during unit testing. * This uses a specific version of the schema identified by a git commit hash. */ String GLEAN_PING_SCHEMA_GIT_HASH = "0248519" String GLEAN_PING_SCHEMA_GIT_HASH = "64b852c" String GLEAN_PING_SCHEMA_URL = "https://raw.githubusercontent.com/mozilla-services/mozilla-pipeline-schemas/$GLEAN_PING_SCHEMA_GIT_HASH/schemas/glean/baseline/baseline.1.schema.json" android { Loading components/service/glean/docs/pings/custom.md +27 −4 Original line number Diff line number Diff line Loading @@ -3,9 +3,29 @@ Applications can define metrics that are sent in custom pings. Unlike the built-in pings, custom pings are sent explicitly by the application. ## Defining a custom ping Custom pings must be defined in a `pings.yaml` file, which is in the same directory alongside your app's `metrics.yaml` file. Each ping has the following parameters: - `description` (required): A human-readable description of the ping. - `include_client_id` (required): A boolean indicating whether to include the `client_id` in the [`client_info` section](pings.md#The-client_info-section)). For example, to define a custom ping specifically for search information: ```YAML search: description: > A ping to record search data. include_client_id: false ``` ## Sending metrics in a custom ping To send a metric on a custom ping, you must first add the custom ping's name to To send a metric on a custom ping, you add the custom ping's name to the `send_in_pings` parameter in the `metrics.yaml` file. For example, to define a new metric to record the default search engine, which Loading @@ -32,9 +52,12 @@ type, you can add the special value `default` to `send_in_pings`: ## Sending a custom ping To send a custom ping, use `Glean.sendPings`. It is up to the application to call this method at the schedule and cadence that is appropriate for the ping. To send a custom ping, call the `send` method on the `PingType` object that glean generated for your ping. For example, to send the custom ping defined above: ```kotlin Glean.sendPings(listOf("search")) import org.mozilla.yourApplication.GleanMetrics.Pings Pings.search.send() ``` components/service/glean/pings.yaml 0 → 100644 +37 −0 Original line number Diff line number Diff line # 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 http://mozilla.org/MPL/2.0/. # This file defines the built-in pings that are recorded by glean telemetry. They are # automatically converted to Kotlin code at build time using the `glean_parser` # PyPI package. $schema: moz://mozilla.org/schemas/glean/pings/1-0-0 baseline: description: > This ping is intended to provide metrics that are managed by the library itself, and not explicitly set by the application or included in the application's `metrics.yaml` file. The `baseline` ping is automatically sent when the application is moved to the background. include_client_id: true metrics: description: > The `metrics` ping is intended for all of the metrics that are explicitly set by the application or are included in the application's `metrics.yaml` file (except events). The reported data is tied to the ping's *measurement window*, which is the time between the collection of two `metrics` ping. Ideally, this window is expected to be about 24 hours, given that the collection is scheduled daily at 4AM. Data in the `ping_info` section of the ping can be used to infer the length of this window. include_client_id: true events: description: > The events ping's purpose is to transport all of the event metric information. The `events` ping is automatically sent when the application is moved to the background. include_client_id: true components/service/glean/scripts/sdk_generator.gradle +2 −1 Original line number Diff line number Diff line Loading @@ -161,6 +161,7 @@ ext.gleanGenerateMetricsAPI = { args "-s" args "namespace=$fullNamespace" args "$projectDir/metrics.yaml" args "$projectDir/pings.yaml" // If we're building the Glean library itself (rather than an // application using Glean) pass the --allow-reserved flag so we can Loading @@ -183,7 +184,7 @@ ext.gleanGenerateMetricsAPI = { // Only attach the generation task if the metrics file is available. We don't need to // fail hard otherwise, as some 3rd party project might just want metrics included in // glean and nothing more. if (file("$projectDir/metrics.yaml").exists()) { if (file("$projectDir/metrics.yaml").exists() || file("$projectDir/pings.yaml").exists()) { // This is an Android-Gradle plugin 3+-ism. Culted from reading the source, // searching for "registerJavaGeneratingTask", and finding // https://github.com/GoogleCloudPlatform/endpoints-framework-gradle-plugin/commit/2f2b91476fb1c6647791e2c6fe531a47615a1e85. Loading components/service/glean/src/main/java/mozilla/components/service/glean/Glean.kt +40 −51 Original line number Diff line number Diff line Loading @@ -18,7 +18,9 @@ import java.util.UUID import mozilla.components.service.glean.config.Configuration import mozilla.components.service.glean.firstrun.FileFirstRunDetector import mozilla.components.service.glean.GleanMetrics.GleanInternalMetrics import mozilla.components.service.glean.GleanMetrics.Pings import mozilla.components.service.glean.ping.PingMaker import mozilla.components.service.glean.private.PingType import mozilla.components.service.glean.scheduler.GleanLifecycleObserver import mozilla.components.service.glean.scheduler.MetricsPingScheduler import mozilla.components.service.glean.scheduler.PingUploadWorker Loading Loading @@ -60,15 +62,6 @@ open class GleanInternalAPI internal constructor () { internal lateinit var pingStorageEngine: PingStorageEngine companion object { internal const val BASELINE_STORE_NAME = "baseline" internal val BUILTIN_PINGNAMES = listOf( BASELINE_STORE_NAME, MetricsPingScheduler.STORE_NAME, "events" ) } /** * Initialize glean. * Loading Loading @@ -336,15 +329,11 @@ open class GleanInternalAPI internal constructor () { */ fun handleBackgroundEvent() { // Schedule the baseline and event pings sendPingsInternal(listOf(BASELINE_STORE_NAME, "events")) sendPings(listOf(Pings.baseline, Pings.events)) } /** * Send a list of pings by name. * * Only custom pings (pings not managed by Glean itself) will be sent by * this function. If the name of a Glean-managed ping is passed in, an * error is logged to logcat. * Send a list of pings. * * While the collection of metrics into pings happens synchronously, the * ping queuing and ping uploading happens asyncronously. Loading @@ -352,36 +341,9 @@ open class GleanInternalAPI internal constructor () { * * If the ping currently contains no content, it will not be sent. * * @param pingNames List of pings to send. * @param pings List of pings to send. */ fun sendPings(pingNames: List<String>) { val pingsToSend = pingNames.filter { pingName -> if (BUILTIN_PINGNAMES.contains(pingName)) { logger.error("Attempted to send built-in ping $pingName") false } else { true } } // Send pings is a "fire and forget" operation, we don't need to wait on // it and we don't care about its return value. sendPingsInternal(pingsToSend) } /** * Send a list of pings by name. * * While the collection of metrics into pings happens synchronously, the * ping queuing and ping uploading happens asyncronously. * There are no guarantees that this will happen immediately. * * * If the ping currently contains no content, it will not be sent. * * @param pingNames List of pings to send. */ internal fun sendPingsInternal(pingNames: List<String>) = Dispatchers.API.launch { internal fun sendPings(pings: List<PingType>) = Dispatchers.API.launch { if (!isInitialized()) { logger.error("Glean must be initialized before sending pings.") return@launch Loading @@ -393,10 +355,10 @@ open class GleanInternalAPI internal constructor () { } val pingSerializationTasks = mutableListOf<Job>() for (pingName in pingNames) { assembleAndSerializePing(pingName)?.let { for (ping in pings) { assembleAndSerializePing(ping)?.let { pingSerializationTasks.add(it) } ?: logger.debug("No content for ping '$pingName', therefore no ping queued.") } ?: logger.debug("No content for ping '$ping.name', therefore no ping queued.") } // If any ping is being serialized to disk, wait for the to finish before spinning up Loading @@ -409,20 +371,47 @@ open class GleanInternalAPI internal constructor () { } } /** * Send a list of pings by name. * * Each ping will be looked up in the known instances of [PingType]. If the * ping isn't known, an error is logged and the ping isn't queued for uploading. * * While the collection of metrics into pings happens synchronously, the * ping queuing and ping uploading happens asyncronously. * There are no guarantees that this will happen immediately. * * If the ping currently contains no content, it will not be sent. * * @param pingNames List of ping names to send. * @return true if any pings had content and were queued for uploading */ internal fun sendPingsByName(pingNames: List<String>): Job? { val pings = pingNames.mapNotNull { pingName -> PingType.pingRegistry.get(pingName)?.let { it } ?: run { logger.error("Attempted to send unknown ping '$pingName'") null } } return sendPings(pings) } /** * Collect and assemble the ping and serialize the ping to be read when uploaded, but only if * glean is initialized, upload is enabled, and there is ping data to send. * * @param pingName This is the ping store/name for which to build and schedule the ping * @param ping This is the object describing the ping */ internal fun assembleAndSerializePing(pingName: String): Job? { internal fun assembleAndSerializePing(ping: PingType): Job? { // Since the pingMaker.collect() function returns null if there is nothing to send we can // use this to avoid sending an empty ping return pingMaker.collect(pingName)?.let { pingContent -> return pingMaker.collect(ping)?.let { pingContent -> // Store the serialized ping to file for PingUploadWorker to read and upload when the // schedule is triggered val pingId = UUID.randomUUID() pingStorageEngine.store(pingId, makePath(pingName, pingId), pingContent) pingStorageEngine.store(pingId, makePath(ping.name, pingId), pingContent) } } Loading Loading
components/service/glean/build.gradle +1 −1 Original line number Diff line number Diff line Loading @@ -14,7 +14,7 @@ apply plugin: 'kotlin-android' * created during unit testing. * This uses a specific version of the schema identified by a git commit hash. */ String GLEAN_PING_SCHEMA_GIT_HASH = "0248519" String GLEAN_PING_SCHEMA_GIT_HASH = "64b852c" String GLEAN_PING_SCHEMA_URL = "https://raw.githubusercontent.com/mozilla-services/mozilla-pipeline-schemas/$GLEAN_PING_SCHEMA_GIT_HASH/schemas/glean/baseline/baseline.1.schema.json" android { Loading
components/service/glean/docs/pings/custom.md +27 −4 Original line number Diff line number Diff line Loading @@ -3,9 +3,29 @@ Applications can define metrics that are sent in custom pings. Unlike the built-in pings, custom pings are sent explicitly by the application. ## Defining a custom ping Custom pings must be defined in a `pings.yaml` file, which is in the same directory alongside your app's `metrics.yaml` file. Each ping has the following parameters: - `description` (required): A human-readable description of the ping. - `include_client_id` (required): A boolean indicating whether to include the `client_id` in the [`client_info` section](pings.md#The-client_info-section)). For example, to define a custom ping specifically for search information: ```YAML search: description: > A ping to record search data. include_client_id: false ``` ## Sending metrics in a custom ping To send a metric on a custom ping, you must first add the custom ping's name to To send a metric on a custom ping, you add the custom ping's name to the `send_in_pings` parameter in the `metrics.yaml` file. For example, to define a new metric to record the default search engine, which Loading @@ -32,9 +52,12 @@ type, you can add the special value `default` to `send_in_pings`: ## Sending a custom ping To send a custom ping, use `Glean.sendPings`. It is up to the application to call this method at the schedule and cadence that is appropriate for the ping. To send a custom ping, call the `send` method on the `PingType` object that glean generated for your ping. For example, to send the custom ping defined above: ```kotlin Glean.sendPings(listOf("search")) import org.mozilla.yourApplication.GleanMetrics.Pings Pings.search.send() ```
components/service/glean/pings.yaml 0 → 100644 +37 −0 Original line number Diff line number Diff line # 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 http://mozilla.org/MPL/2.0/. # This file defines the built-in pings that are recorded by glean telemetry. They are # automatically converted to Kotlin code at build time using the `glean_parser` # PyPI package. $schema: moz://mozilla.org/schemas/glean/pings/1-0-0 baseline: description: > This ping is intended to provide metrics that are managed by the library itself, and not explicitly set by the application or included in the application's `metrics.yaml` file. The `baseline` ping is automatically sent when the application is moved to the background. include_client_id: true metrics: description: > The `metrics` ping is intended for all of the metrics that are explicitly set by the application or are included in the application's `metrics.yaml` file (except events). The reported data is tied to the ping's *measurement window*, which is the time between the collection of two `metrics` ping. Ideally, this window is expected to be about 24 hours, given that the collection is scheduled daily at 4AM. Data in the `ping_info` section of the ping can be used to infer the length of this window. include_client_id: true events: description: > The events ping's purpose is to transport all of the event metric information. The `events` ping is automatically sent when the application is moved to the background. include_client_id: true
components/service/glean/scripts/sdk_generator.gradle +2 −1 Original line number Diff line number Diff line Loading @@ -161,6 +161,7 @@ ext.gleanGenerateMetricsAPI = { args "-s" args "namespace=$fullNamespace" args "$projectDir/metrics.yaml" args "$projectDir/pings.yaml" // If we're building the Glean library itself (rather than an // application using Glean) pass the --allow-reserved flag so we can Loading @@ -183,7 +184,7 @@ ext.gleanGenerateMetricsAPI = { // Only attach the generation task if the metrics file is available. We don't need to // fail hard otherwise, as some 3rd party project might just want metrics included in // glean and nothing more. if (file("$projectDir/metrics.yaml").exists()) { if (file("$projectDir/metrics.yaml").exists() || file("$projectDir/pings.yaml").exists()) { // This is an Android-Gradle plugin 3+-ism. Culted from reading the source, // searching for "registerJavaGeneratingTask", and finding // https://github.com/GoogleCloudPlatform/endpoints-framework-gradle-plugin/commit/2f2b91476fb1c6647791e2c6fe531a47615a1e85. Loading
components/service/glean/src/main/java/mozilla/components/service/glean/Glean.kt +40 −51 Original line number Diff line number Diff line Loading @@ -18,7 +18,9 @@ import java.util.UUID import mozilla.components.service.glean.config.Configuration import mozilla.components.service.glean.firstrun.FileFirstRunDetector import mozilla.components.service.glean.GleanMetrics.GleanInternalMetrics import mozilla.components.service.glean.GleanMetrics.Pings import mozilla.components.service.glean.ping.PingMaker import mozilla.components.service.glean.private.PingType import mozilla.components.service.glean.scheduler.GleanLifecycleObserver import mozilla.components.service.glean.scheduler.MetricsPingScheduler import mozilla.components.service.glean.scheduler.PingUploadWorker Loading Loading @@ -60,15 +62,6 @@ open class GleanInternalAPI internal constructor () { internal lateinit var pingStorageEngine: PingStorageEngine companion object { internal const val BASELINE_STORE_NAME = "baseline" internal val BUILTIN_PINGNAMES = listOf( BASELINE_STORE_NAME, MetricsPingScheduler.STORE_NAME, "events" ) } /** * Initialize glean. * Loading Loading @@ -336,15 +329,11 @@ open class GleanInternalAPI internal constructor () { */ fun handleBackgroundEvent() { // Schedule the baseline and event pings sendPingsInternal(listOf(BASELINE_STORE_NAME, "events")) sendPings(listOf(Pings.baseline, Pings.events)) } /** * Send a list of pings by name. * * Only custom pings (pings not managed by Glean itself) will be sent by * this function. If the name of a Glean-managed ping is passed in, an * error is logged to logcat. * Send a list of pings. * * While the collection of metrics into pings happens synchronously, the * ping queuing and ping uploading happens asyncronously. Loading @@ -352,36 +341,9 @@ open class GleanInternalAPI internal constructor () { * * If the ping currently contains no content, it will not be sent. * * @param pingNames List of pings to send. * @param pings List of pings to send. */ fun sendPings(pingNames: List<String>) { val pingsToSend = pingNames.filter { pingName -> if (BUILTIN_PINGNAMES.contains(pingName)) { logger.error("Attempted to send built-in ping $pingName") false } else { true } } // Send pings is a "fire and forget" operation, we don't need to wait on // it and we don't care about its return value. sendPingsInternal(pingsToSend) } /** * Send a list of pings by name. * * While the collection of metrics into pings happens synchronously, the * ping queuing and ping uploading happens asyncronously. * There are no guarantees that this will happen immediately. * * * If the ping currently contains no content, it will not be sent. * * @param pingNames List of pings to send. */ internal fun sendPingsInternal(pingNames: List<String>) = Dispatchers.API.launch { internal fun sendPings(pings: List<PingType>) = Dispatchers.API.launch { if (!isInitialized()) { logger.error("Glean must be initialized before sending pings.") return@launch Loading @@ -393,10 +355,10 @@ open class GleanInternalAPI internal constructor () { } val pingSerializationTasks = mutableListOf<Job>() for (pingName in pingNames) { assembleAndSerializePing(pingName)?.let { for (ping in pings) { assembleAndSerializePing(ping)?.let { pingSerializationTasks.add(it) } ?: logger.debug("No content for ping '$pingName', therefore no ping queued.") } ?: logger.debug("No content for ping '$ping.name', therefore no ping queued.") } // If any ping is being serialized to disk, wait for the to finish before spinning up Loading @@ -409,20 +371,47 @@ open class GleanInternalAPI internal constructor () { } } /** * Send a list of pings by name. * * Each ping will be looked up in the known instances of [PingType]. If the * ping isn't known, an error is logged and the ping isn't queued for uploading. * * While the collection of metrics into pings happens synchronously, the * ping queuing and ping uploading happens asyncronously. * There are no guarantees that this will happen immediately. * * If the ping currently contains no content, it will not be sent. * * @param pingNames List of ping names to send. * @return true if any pings had content and were queued for uploading */ internal fun sendPingsByName(pingNames: List<String>): Job? { val pings = pingNames.mapNotNull { pingName -> PingType.pingRegistry.get(pingName)?.let { it } ?: run { logger.error("Attempted to send unknown ping '$pingName'") null } } return sendPings(pings) } /** * Collect and assemble the ping and serialize the ping to be read when uploaded, but only if * glean is initialized, upload is enabled, and there is ping data to send. * * @param pingName This is the ping store/name for which to build and schedule the ping * @param ping This is the object describing the ping */ internal fun assembleAndSerializePing(pingName: String): Job? { internal fun assembleAndSerializePing(ping: PingType): Job? { // Since the pingMaker.collect() function returns null if there is nothing to send we can // use this to avoid sending an empty ping return pingMaker.collect(pingName)?.let { pingContent -> return pingMaker.collect(ping)?.let { pingContent -> // Store the serialized ping to file for PingUploadWorker to read and upload when the // schedule is triggered val pingId = UUID.randomUUID() pingStorageEngine.store(pingId, makePath(pingName, pingId), pingContent) pingStorageEngine.store(pingId, makePath(ping.name, pingId), pingContent) } } Loading