Commit 5d8c9003 authored by Michael Comella's avatar Michael Comella Committed by Jeff Boek
Browse files

For #12802: add StorageStats glean metrics.

parent 70c66185
Loading
Loading
Loading
Loading
+79 −0
Original line number Diff line number Diff line
@@ -3236,3 +3236,82 @@ autoplay:
    notification_emails:
      - fenix-core@mozilla.com
    expires: "2021-02-01"

storage.stats:
  query_stats_duration:
    send_in_pings:
      - metrics
    type: timing_distribution
    description: >
      How long it took to query the device for the StorageStats that contain the
      file size information. The docs say it may be expensive so we want to
      ensure it's not too expensive.  This value is only available on Android
      8+.
    bugs:
      - https://github.com/mozilla-mobile/fenix/issues/12802
    data_reviews:
      - todo
    notification_emails:
      - fenix-core@mozilla.com
      - perf-android-fe@mozilla.com
      - mcomella@mozilla.com
    expires: "2020-12-21"
  app_bytes:
    send_in_pings:
      - metrics
    type: memory_distribution
    description: >
      The size of the app's APK and related files as installed: this is expected
      to be larger than download size. This is the output of
      [StorageStats.getAppBytes](https://developer.android.com/reference/android/app/usage/StorageStats#getAppBytes())
      so see that for details. This value is only available on Android 8+. A
      similar value may be available on the Google Play dashboard: we can use
      this value to see if that value is reliable enough.
    memory_unit: byte
    bugs:
      - https://github.com/mozilla-mobile/fenix/issues/12802
    data_reviews:
      - todo
    notification_emails:
      - fenix-core@mozilla.com
      - perf-android-fe@mozilla.com
      - mcomella@mozilla.com
    expires: "2020-12-21"
  cache_bytes:
    send_in_pings:
      - metrics
    type: memory_distribution
    description: >
      The size of all cached data in the app. This is the output of
      [StorageStats.getCacheBytes](https://developer.android.com/reference/android/app/usage/StorageStats#getCacheBytes())
      so see that for details. This value is only available on Android 8+.
    memory_unit: byte
    bugs:
      - https://github.com/mozilla-mobile/fenix/issues/12802
    data_reviews:
      - todo
    notification_emails:
      - fenix-core@mozilla.com
      - perf-android-fe@mozilla.com
      - mcomella@mozilla.com
    expires: "2020-12-21"
  data_dir_bytes:
    send_in_pings:
      - metrics
    type: memory_distribution
    description: >
      The size of all data minus `cache_bytes`. This is the output of
      [StorageStats.getDataBytes](https://developer.android.com/reference/android/app/usage/StorageStats#getDataBytes())
      except we subtract the value of `cache_bytes` so the cache is not measured
      redundantly; see that method for details. This value is only available on
      Android 8+.
    memory_unit: byte
    bugs:
      - https://github.com/mozilla-mobile/fenix/issues/12802
    data_reviews:
      - todo
    notification_emails:
      - fenix-core@mozilla.com
      - perf-android-fe@mozilla.com
      - mcomella@mozilla.com
    expires: "2020-12-21"
+13 −0
Original line number Diff line number Diff line
@@ -44,6 +44,7 @@ import org.mozilla.fenix.components.Components
import org.mozilla.fenix.components.metrics.MetricServiceType
import org.mozilla.fenix.ext.resetPoliciesAfter
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.perf.StorageStatsMetrics
import org.mozilla.fenix.perf.StartupTimeline
import org.mozilla.fenix.push.PushFxaIntegration
import org.mozilla.fenix.push.WebPushEngineIntegration
@@ -205,12 +206,24 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
            }
        }

        fun queueMetrics() {
            if (SDK_INT >= Build.VERSION_CODES.O) { // required by StorageStatsMetrics.
                taskQueue.runIfReadyOrQueue {
                    // Because it may be slow to capture the storage stats, it might be preferred to
                    // create a WorkManager task for this metric, however, I ran out of
                    // implementation time and WorkManager is harder to test.
                    StorageStatsMetrics.report(this.applicationContext)
                }
            }
        }

        initQueue()

        // We init these items in the visual completeness queue to avoid them initing in the critical
        // startup path, before the UI finishes drawing (i.e. visual completeness).
        queueInitExperiments()
        queueInitStorageAndServices()
        queueMetrics()
    }

    private fun startMetricsIfEnabled() {
+66 −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/. */

package org.mozilla.fenix.perf

import android.app.usage.StorageStats
import android.app.usage.StorageStatsManager
import android.content.Context
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.annotation.VisibleForTesting
import androidx.annotation.VisibleForTesting.PRIVATE
import androidx.annotation.WorkerThread
import androidx.core.content.getSystemService
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.mozilla.fenix.GleanMetrics.StorageStats as Metrics

/**
 * A collection of functions related to measuring the [StorageStats] of the application such as data
 * dir size.
 *
 * Unfortunately, this API is only available on API 26+ so the data will only be reported for those
 * platforms.
 */
@RequiresApi(Build.VERSION_CODES.O) // StorageStatsManager
object StorageStatsMetrics {

    fun report(context: Context) {
        GlobalScope.launch(Dispatchers.IO) {
            reportSync(context)
        }
    }

    // I couldn't get runBlockingTest to work correctly so I moved the functionality under test to
    // a synchronous function.
    @VisibleForTesting(otherwise = PRIVATE)
    @WorkerThread // queryStatsForUid
    fun reportSync(context: Context) {
        // I don't expect this to ever be null so we don't report if so.
        context.getSystemService<StorageStatsManager>()?.let { storageStatsManager ->
            val appInfo = context.applicationInfo
            val storageStats = Metrics.queryStatsDuration.measure {
                // The docs say queryStatsForPackage may be slower if the app uses
                // android:sharedUserId so we the suggested alternative.
                //
                // The docs say this may be slow:
                // > This method may take several seconds to complete, so it should only be called
                // > from a worker thread.
                //
                // So we call from a worker thread and measure the duration to make sure it's not
                // too slow.
                storageStatsManager.queryStatsForUid(appInfo.storageUuid, appInfo.uid)
            }

            // dataBytes includes the cache so we subtract it.
            val justDataDirBytes = storageStats.dataBytes - storageStats.cacheBytes

            Metrics.dataDirBytes.accumulate(justDataDirBytes)
            Metrics.appBytes.accumulate(storageStats.appBytes)
            Metrics.cacheBytes.accumulate(storageStats.cacheBytes)
        }
    }
}
+61 −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/. */

package org.mozilla.fenix.perf

import android.app.usage.StorageStats
import android.app.usage.StorageStatsManager
import android.content.Context
import androidx.core.content.getSystemService
import io.mockk.MockKAnnotations
import io.mockk.every
import io.mockk.impl.annotations.RelaxedMockK
import mozilla.components.service.glean.testing.GleanTestRule
import mozilla.components.support.test.robolectric.testContext
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.GleanMetrics.StorageStats as Metrics

@RunWith(FenixRobolectricTestRunner::class) // gleanTestRule
class StorageStatsMetricsTest {

    @get:Rule
    val gleanTestRule = GleanTestRule(testContext)

    @RelaxedMockK private lateinit var mockContext: Context
    @RelaxedMockK private lateinit var storageStats: StorageStats

    @Before
    fun setUp() {
        MockKAnnotations.init(this)

        every {
            mockContext.getSystemService<StorageStatsManager>()?.queryStatsForUid(any(), any())
        } returns storageStats
    }

    @Test
    fun `WHEN reporting THEN the values from the storageStats are accumulated`() {
        every { storageStats.appBytes } returns 100
        every { storageStats.cacheBytes } returns 200
        every { storageStats.dataBytes } returns 1000

        StorageStatsMetrics.reportSync(mockContext)

        assertEquals(100, Metrics.appBytes.testGetValue().sum)
        assertEquals(200, Metrics.cacheBytes.testGetValue().sum)
        assertEquals(800, Metrics.dataDirBytes.testGetValue().sum)
    }

    @Test
    fun `WHEN reporting THEN the query duration is measured`() {
        StorageStatsMetrics.reportSync(mockContext)
        assertTrue(Metrics.queryStatsDuration.testHasValue())
    }
}
+4 −0
Original line number Diff line number Diff line
@@ -311,6 +311,10 @@ The following metrics are added to the ping:
| search.default_engine.code |[string](https://mozilla.github.io/glean/book/user/metrics/string.html) |If the search engine is pre-loaded with Fenix this value will be the search engine identifier. If it's a custom search engine (defined: https://github.com/mozilla-mobile/fenix/issues/1607) the value will be "custom"  |[1](https://github.com/mozilla-mobile/fenix/pull/1606), [2](https://github.com/mozilla-mobile/fenix/pull/5216)||2020-10-01 | |
| search.default_engine.name |[string](https://mozilla.github.io/glean/book/user/metrics/string.html) |If the search engine is pre-loaded with Fenix this value will be the search engine name. If it's a custom search engine (defined: https://github.com/mozilla-mobile/fenix/issues/1607) the value will be "custom"  |[1](https://github.com/mozilla-mobile/fenix/pull/1606), [2](https://github.com/mozilla-mobile/fenix/pull/5216)||2020-10-01 | |
| search.default_engine.submission_url |[string](https://mozilla.github.io/glean/book/user/metrics/string.html) |If the search engine is pre-loaded with Fenix this value will be he base URL we use to build the search query for the search engine. For example: https://mysearchengine.com/?query=%s. If it's a custom search engine (defined: https://github.com/mozilla-mobile/fenix/issues/1607) the value will be "custom"  |[1](https://github.com/mozilla-mobile/fenix/pull/1606), [2](https://github.com/mozilla-mobile/fenix/pull/5216)||2020-10-01 | |
| storage.stats.app_bytes |[memory_distribution](https://mozilla.github.io/glean/book/user/metrics/memory_distribution.html) |The size of the app's APK and related files as installed: this is expected to be larger than download size. This is the output of [StorageStats.getAppBytes](https://developer.android.com/reference/android/app/usage/StorageStats#getAppBytes()) so see that for details. This value is only available on Android 8+. A similar value may be available on the Google Play dashboard: we can use this value to see if that value is reliable enough.  |[1](todo)||2020-12-21 | |
| storage.stats.cache_bytes |[memory_distribution](https://mozilla.github.io/glean/book/user/metrics/memory_distribution.html) |The size of all cached data in the app. This is the output of [StorageStats.getCacheBytes](https://developer.android.com/reference/android/app/usage/StorageStats#getCacheBytes()) so see that for details. This value is only available on Android 8+.  |[1](todo)||2020-12-21 | |
| storage.stats.data_dir_bytes |[memory_distribution](https://mozilla.github.io/glean/book/user/metrics/memory_distribution.html) |The size of all data minus `cache_bytes`. This is the output of [StorageStats.getDataBytes](https://developer.android.com/reference/android/app/usage/StorageStats#getDataBytes()) except we subtract the value of `cache_bytes` so the cache is not measured redundantly; see that method for details. This value is only available on Android 8+.  |[1](todo)||2020-12-21 | |
| storage.stats.query_stats_duration |[timing_distribution](https://mozilla.github.io/glean/book/user/metrics/timing_distribution.html) |How long it took to query the device for the StorageStats that contain the file size information. The docs say it may be expensive so we want to ensure it's not too expensive.  This value is only available on Android 8+.  |[1](todo)||2020-12-21 | |

## startup-timeline