MemoryUsageMetric


@ExperimentalMetricApi
class MemoryUsageMetric : TraceMetric


Metric for tracking the memory usage of the target application.

Summary

Public constructors

MemoryUsageMetric(
    mode: MemoryUsageMetric.Mode,
    subMetrics: List<MemoryUsageMetric.SubMetric>,
    processNameSuffix: String,
    metricNameSuffix: String
)

Public functions

open List<Metric.Measurement>
getMeasurements(
    captureInfo: Metric.CaptureInfo,
    traceSession: TraceProcessor.Session
)

Get the metric result for a given iteration given information about the target process and a TraceProcessor session

Public constructors

MemoryUsageMetric

MemoryUsageMetric(
    mode: MemoryUsageMetric.Mode,
    subMetrics: List<MemoryUsageMetric.SubMetric> = listOf(SubMetric.HeapSize, SubMetric.RssAnon, SubMetric.RssFile, SubMetric.Gpu),
    processNameSuffix: String = "",
    metricNameSuffix: String = processNameSuffix.replace(oldValue = ":", newValue = "_")
)
Parameters
mode: MemoryUsageMetric.Mode

There are two modes for measurement - Last, which represents the last observed value during an iteration, and Max, which represents the largest sample observed per measurement.

subMetrics: List<MemoryUsageMetric.SubMetric> = listOf(SubMetric.HeapSize, SubMetric.RssAnon, SubMetric.RssFile, SubMetric.Gpu)

By default, reports:

  • memoryRssAnonKb - Measures anonymous resident set size memory. This represents memory allocated directly by the process—such as via malloc or mmap—that is not backed by any file on disk. It is often the primary indicator of the app's dynamic memory consumption.

  • memoryRssAnonFileKb - Measures memory used to map files from disk into the process's address space. This includes shared libraries, dex files, and other resource assets loaded by the application.

  • memoryHeapSizeKb - Tracks the total size of the Android Runtime (ART) heap. These samples are typically captured immediately after a Garbage Collection (GC) event, providing a look at the "live" set of objects in the application's managed memory.

  • memoryGpuKb - Measures the amount of GPU-specific memory allocated for the process. This is particularly useful for identifying high memory usage related to textures, shaders, or other graphics-heavy components.

By passing a custom subMetrics list, you can enable other SubMetrics.

processNameSuffix: String = ""

A suffix appended to the app's package name for subprocesses. This is useful when there are separate subprocesses of the app.

metricNameSuffix: String = processNameSuffix.replace(oldValue = ":", newValue = "_")

A suffix appended to the metric names. Use this to distinguish metrics collected from different subprocesses in the app. Defaults to processNameSuffix with ":" replaced by "_".

Public functions

getMeasurements

open fun getMeasurements(
    captureInfo: Metric.CaptureInfo,
    traceSession: TraceProcessor.Session
): List<Metric.Measurement>

Get the metric result for a given iteration given information about the target process and a TraceProcessor session

import androidx.benchmark.macro.ExperimentalMetricApi
import androidx.benchmark.macro.TraceMetric
import androidx.benchmark.traceprocessor.TraceProcessor

/**
 * Calculates latency of sections where begin and end trace points are in different processes.
 *
 * @param beginPointName Name of begin tracepoint.
 * @param endPointName Name of end tracepoint.
 * @param eventName Name of the final metric that is spit out after the test run. The metric
 *   name will be {$eventName}LatencyMillis.
 */
@OptIn(ExperimentalMetricApi::class)
class CrossProcessLatencyMetricSample(
    private val beginPointName: String,
    private val endPointName: String,
    private val eventName: String,
) : TraceMetric() {
    @OptIn(ExperimentalMetricApi::class)
    override fun getMeasurements(
        captureInfo: CaptureInfo,
        traceSession: TraceProcessor.Session,
    ): List<Measurement> {
        val query =
            """
         INCLUDE perfetto module slices.with_context;
         SELECT
             event,
             ts2-ts1 AS latency_in_nanos
         FROM
         (
           (SELECT
               ts AS ts1,
               "event" AS event,
               LOWER(TRIM(process_name)) as process_name_1
               FROM thread_slice WHERE name = '${beginPointName}'
           )
           LEFT JOIN
           (SELECT
               ts AS ts2,
               "event" AS event,
               LOWER(TRIM(process_name)) as process_name_2
               FROM thread_slice WHERE name = '${endPointName}'
           )
           USING (event)
         )
           """
                .trimIndent() // maybe add checks for process name etc too in query

        val rowSequence = traceSession.query(query)
        // First row (or null) is returned.
        val latencyResultNanos = rowSequence.firstOrNull()?.long("latency_in_nanos")
        return if (latencyResultNanos != null) {
            listOf(Measurement(eventName + "LatencyMillis", latencyResultNanos / 1_000_000.0))
        } else {
            emptyList()
        }
    }
}