En esta página, se proporciona una descripción general del framework de telemetría del kernel de Extended Berkeley Packet Filter (eBPF) en Android, en la que se describe la arquitectura del framework, las métricas integradas y cómo agregar nuevas métricas del kernel.
Android incluye un framework de telemetría del kernel de eBPF unificado para recopilar métricas del kernel de baja sobrecarga y exponerlas a servicios del espacio de usuario, como statsd. Al adjuntar programas de eBPF portátiles a los puntos de seguimiento del kernel y
a los kprobes, el framework agrega eventos del kernel de alta frecuencia en su lugar y
exporta histogramas o contadores compactos sin modificar la
imagen genérica del kernel (GKI) ni interrumpir la interfaz del módulo del kernel (KMI).
Los enfoques tradicionales de la telemetría del kernel se basan en nodos procfs o sysfs personalizados, parches del kernel fuera del árbol o hooks del proveedor que requieren una canalización personalizada en el kernel, SELinux, la interfaz nativa de Java (JNI) y los servicios del espacio de usuario.
El framework de telemetría del kernel de eBPF estandariza esta canalización en los kernels comunes de Android (ACKs) y proporciona los siguientes beneficios:
- Evita la rotación de ABI y KMI del kernel: Adjunta programas de eBPF a los puntos de seguimiento o kprobes del kernel existentes sin modificar el código fuente del kernel, cambiar la interfaz binaria de la aplicación del kernel (ABI) ni agregar hooks del proveedor de Android, como
TRACE_ANDROID_VH_*. Los fabricantes de dispositivos y los socios de sistema en chip (SoC) pueden usar la telemetría de eBPF en lugar de agregar nuevos hooks del proveedor de telemetría o para migrar de los hooks del proveedor existentes. - Mantiene la portabilidad en las versiones del kernel: Usa BPF Compile Once - Run Everywhere (CO-RE) y BPF Type Format (BTF) para que un solo programa de eBPF compilado se adapte automáticamente a los cambios internos del diseño de la estructura del kernel en las versiones de GKI, como
android14-6.1y versiones posteriores. - Minimiza la sobrecarga de estado del sistema: Filtra y agrega eventos del kernel de alta frecuencia directamente en la fuente en histogramas logarítmicos base 2 (
log2) compactos de 32 buckets, por lo general, menos de 1 KB por mapa de BPF. El espacio de usuario recupera las entradas de mapa agregadas una vez al día con una sola llamada al sistemaBPF_MAP_LOOKUP_BATCHpor lotes. - Simplifica la integración de SELinux y JNI: Fija los mapas de telemetría en
/sys/fs/bpf/kernelstats/para aplicar la herencia de tipo SELinuxfs_bpf_kernelstatsa nivel de directorio y resuelve los desplazamientos de miembros de la estructura en el tiempo de ejecución en JNI con metadatos de BTF para eliminar el código de análisis de C++ personalizado cuando se agregan métricas nuevas.
Para obtener más información sobre cómo Android carga y fija programas y mapas de eBPF, consulta Extiende el kernel con eBPF.
Descripción general de la arquitectura
La canalización de telemetría del kernel de eBPF separa los componentes de infraestructura genéricos de las definiciones específicas de las métricas, de modo que agregar una nueva métrica del kernel requiere cambios mínimos en el código. En la siguiente tabla, se describen los componentes de la canalización:
| Componente | Rol en la canalización | Alcance |
|---|---|---|
Cargador de BPF, bpfloader | Carga programas de eBPF compilados desde /system/etc/bpf/kernelstats/ durante el inicio si es compatible con el kernel en ejecución y fija mapas en /sys/fs/bpf/kernelstats/. Omite la carga de forma limpia cuando está inhabilitado o no es compatible. | Genérico |
Política de SELinux, fs_bpf_kernelstats | Aplica etiquetas de SELinux a nivel de directorio a /sys/fs/bpf/kernelstats/ para que system_server pueda leer mapas de telemetría sin actualizaciones de la política de SELinux por métrica. | Genérico |
Programa de eBPF en system/bpfprogs/telemetry/ | Se adjunta a los puntos de seguimiento o kprobes del kernel, filtra eventos y agrega métricas en mapas de BPF. | Personalizado por métrica |
| Lector de BTF de JNI | Analiza los metadatos de BTF del objeto binario compilado de BPF Executable and Linkable Format (ELF) en el tiempo de ejecución, almacena en caché los desplazamientos de miembros de la estructura y realiza lecturas de mapas por lotes en arrays primitivos de Java. | Genérico |
Extractor del espacio de usuario en statsd | Extrae mapas de histogramas acumulativos según una programación diaria, calcula deltas en el historial almacenado en caché y compila átomos de extracción StatsEvent con tipo seguro. | Auxiliar genérico con una sola entrada case por átomo |
| Esquema de telemetría | Define el protobuf de átomo de extracción statsd con tipo seguro y las agregaciones de histogramas o métricas de valores del servidor. | Personalizado por métrica |
Modelos de consumo del espacio de usuario
Cuando diseñes una métrica de telemetría del kernel, elige entre dos modelos de consumo del espacio de usuario según si la métrica representa un estado acumulativo o una secuencia de eventos ordenada. En la siguiente tabla, se comparan los dos modelos:
| Propiedad | Acumuladores | Transmisiones |
|---|---|---|
| Definición | Contadores o histogramas acumulativos con estado y que aumentan de forma monotónica | Secuencia ordenada de eventos del kernel con marca de tiempo |
| Patrón de acceso | El espacio de usuario sondea a pedido, por ejemplo, una vez cada 24 horas a través de statsd. | El espacio de usuario consume de forma continua desde un búfer de anillo para evitar eventos descartados. |
| Tipos de mapas de BPF típicos | BPF_MAP_TYPE_HASH, BPF_MAP_TYPE_LRU_HASH, BPF_MAP_TYPE_PERCPU_HASH, BPF_MAP_TYPE_LRU_PERCPU_HASH | BPF_MAP_TYPE_RINGBUF |
| Modelo de simultaneidad | Las búsquedas de mapas del kernel son atómicas; varios lectores pueden sondear sin bloqueo del espacio de usuario. | La semántica de lectura destructiva requiere un solo subproceso de lector que se envíe a los consumidores. |
| Ejemplos de casos de uso | Histogramas de latencia de administración de la memoria, tamaños de asignación de búfer de acceso directo a la memoria (DMA-BUF), iteradores de fuente de activación | Eventos de seguimiento del programador filtrados, seguimientos de energía del subsistema de alta frecuencia |
Métricas de telemetría del kernel integradas
Android incluye programas de telemetría del kernel de eBPF integrados que funcionan en kernels de GKI 6.1 y versiones posteriores, incluidos android14-6.1, android15-6.6, android16-6.12 y android17-6.18. Cada métrica de latencia y tamaño usa un histograma exponencial (log2) de 32 buckets agregado en un período de 24 horas. En la siguiente tabla, se enumeran las métricas de telemetría del kernel integradas:
| Átomo de métrica | Subsistema | Descripción | Clave de dimensión |
|---|---|---|---|
KswapdLatency | Administración de la memoria | Daemon de intercambio de kernel, kswapd, latencia de ejecución de activación a suspensión en microsegundos | Acceso a memoria no uniforme (NUMA) node_id |
DirectReclaimLatency | Administración de la memoria | Latencia de recuperación de memoria directa síncrona en microsegundos | Estado oom_score_adj de proceso sin memoria (OOM) |
CompactionDuration | Administración de la memoria | Duración de la ejecución de la compactación de memoria en todo el sistema en microsegundos, que reemplaza los hooks del proveedor de compactación heredados | En todo el sistema |
CmaAllocationLatency | Administración de la memoria | Latencia de asignación de página del asignador de memoria contigua (CMA) en microsegundos | Nombre del montón de CMA |
DmabufRequestSizes | Administración de la memoria | Tamaños de solicitud de asignación de DMA-BUF en bytes | Nombre del montón del exportador |
SuspendResumeLatency | Administración de energía | Latencia en las etapas de suspensión y reanudación del kernel en microsegundos | Etapa de acción de suspensión |
Agrega una métrica de telemetría del kernel
Para agregar una nueva métrica de acumulador a la canalización de telemetría del kernel de eBPF, completa los siguientes pasos en orden:
- Escribe el programa de eBPF en
system/bpfprogs/telemetry/y compila el objeto binario en/system/etc/bpf/kernelstats/. - Verifica la compatibilidad del diseño de la estructura de BTF para el lector de BTF de JNI genérico.
- Registra el átomo de extracción en
statsd. - Define el esquema de protobuf del átomo de extracción.
En las siguientes secciones, se usa DirectReclaimLatency (directReclaim.c) como ejemplo.
Escribe el programa de eBPF
En system/bpfprogs/telemetry/, incluye <bpf_telemetry.h> y define la clave
del mapa y el mapa del acumulador de salida. Usa identificadores de números enteros o enumeraciones en las claves de mapa de BPF en lugar de cadenas sin procesar para minimizar el espacio en memoria del mapa y preservar la privacidad:
#include <android_bpf_defs.h>
#include <bpf/bpf_core_read.h>
#include <bpf/bpf_helpers.h>
#include <bpf_telemetry.h>
#include <stdint.h>
struct direct_reclaim_key {
uint32_t process_state;
};
#define DIRECT_RECLAIM_START_MAP_ENTRIES 128
#define DIRECT_RECLAIM_OUTPUT_MAP_ENTRIES 8
// Temporary map to store start time. Key: pid (thread_id), Value: start_time_ns
DEFINE_BPF_MAP_GRO(direct_reclaim_start_map, HASH, uint32_t, uint64_t,
DIRECT_RECLAIM_START_MAP_ENTRIES, AID_SYSTEM);
// Output map. Key: direct_reclaim_key, Value: accumulator_value (32 log2 buckets)
DEFINE_BPF_MAP_GRO(direct_reclaim_latency_map, HASH, struct direct_reclaim_key,
struct accumulator_value, DIRECT_RECLAIM_OUTPUT_MAP_ENTRIES,
AID_SYSTEM);
En los controladores de puntos de seguimiento, registra la marca de tiempo de inicio en la entrada, calcula el bucket de latencia log2 en la salida con GET_LATENCY_BUCKET y aumenta la entrada del mapa del acumulador con INCREMENT_ACCUMULATOR:
DEFINE_BPF_PROG("tp_btf/mm_vmscan_direct_reclaim_begin", AID_ROOT, AID_SYSTEM,
tracepoint_vmscan_mm_vmscan_direct_reclaim_begin)(
uint64_t* args) {
(void)args;
uint32_t pid = (uint32_t)bpf_get_current_pid_tgid();
SAVE_FIRST_START_TIME(direct_reclaim_start_map, &pid);
return 0;
}
DEFINE_BPF_PROG("tp_btf/mm_vmscan_direct_reclaim_end", AID_ROOT, AID_SYSTEM,
tracepoint_vmscan_mm_vmscan_direct_reclaim_end)(
uint64_t* args) {
(void)args;
uint32_t pid = (uint32_t)bpf_get_current_pid_tgid();
uint32_t bucket = GET_LATENCY_BUCKET(direct_reclaim_start_map, &pid);
if (bucket == BPF_TELEMETRY_INVALID_BUCKET) return 0;
struct task_struct* task = (struct task_struct*)bpf_get_current_task();
int oom_score_adj = BPF_CORE_READ(task, signal, oom_score_adj);
enum process_state state = get_process_state(oom_score_adj);
struct direct_reclaim_key key = {.process_state = (uint32_t)state};
INCREMENT_ACCUMULATOR(direct_reclaim_latency_map, &key, bucket);
return 0;
}
Verifica la compatibilidad del diseño de la estructura de BTF
No es necesario que escribas código C++ de JNI personalizado cuando agregues un nuevo mapa de histogramas, siempre que las estructuras de clave y valor del mapa usen tipos de números enteros, enumeraciones y arrays fijos estándares. En lugar de convertir las entradas de mapa de BPF en estructuras C codificadas de forma rígida en el tiempo de compilación, el lector de JNI genérico resuelve los diseños de estructura de forma dinámica en el tiempo de ejecución en tres fases:
- Asociación de ELF a mapa: La capa de JNI analiza los metadatos de BTF directamente desde el archivo ELF de BPF compilado en el disco, como
/system/etc/bpf/kernelstats/directReclaim.bpf, con la funciónbtf__parsedelibbpf. - Resolución de tipo BTF: En el primer acceso, el lector compila y almacena en caché una representación estructural de las estructuras C de clave y valor del mapa, y calcula los desplazamientos de bytes acumulativos para los miembros de la estructura, los anchos de bytes de números enteros y enumeraciones, y los tamaños de elementos de array desde el puntero base.
- Búsqueda y serialización por lotes: Cuando
statsdextrae el mapa, la capa de JNI emite un solo comandoBPF_MAP_LOOKUP_BATCHa la llamada al sistemasys_bpfpara leer todas las entradas hash de forma masiva y copia los IDs de clave resueltos y los arrays de buckets directamente en arrayslong[]primitivos de Java con la funciónSetLongArrayRegionde JNI.
Si una estructura C de BPF cambia en una versión futura, la sección de BTF recompilada se actualiza automáticamente y el lector de JNI se adapta en el tiempo de ejecución sin requerir la recompilación de JNI.
Registra el átomo de extracción en statsd
En StatsPullAtomService.java, el auxiliar genérico pullKernelTelemetry lee el mapa de BPF fijado, calcula las deltas de buckets en función de los recuentos almacenados en caché de la extracción anterior y emite entradas StatsEvent. Agregar una nueva métrica de telemetría del kernel requiere solo tres pequeñas adiciones:
// 1. Dispatch the atom tag to pullKernelTelemetry() in onPullAtomLocked():
case FrameworkStatsLog.DIRECT_RECLAIM_LATENCY:
return pullKernelTelemetry(atomTag, data);
// 2. Add the atom tag to the registered atoms list in registerKernelTelemetry():
int[] atoms = {
FrameworkStatsLog.DIRECT_RECLAIM_LATENCY,
// ...
};
// 3. Map the atom tag to its pinned BPF map path:
case FrameworkStatsLog.DIRECT_RECLAIM_LATENCY ->
"/sys/fs/bpf/kernelstats/map_directReclaim_direct_reclaim_latency_map";
Define el protobuf del átomo de extracción
Define el mensaje de átomo de extracción con tipo seguro en frameworks/proto_logging/ para que coincida con la clave de dimensión y el array de histogramas log2 de 32 buckets que produce el mapa de BPF:
message DirectReclaimLatency {
enum ProcessState {
PROCESS_STATE_UNSPECIFIED = 0;
STATE_NATIVE = 1;
STATE_SYSTEM = 2;
STATE_PERSISTENT = 3;
STATE_FOREGROUND = 4;
STATE_VISIBLE = 5;
STATE_PERCEPTIBLE = 6;
STATE_SERVICE_BACKGROUND = 7;
STATE_CACHED = 8;
}
optional ProcessState process_state = 1;
repeated int32 duration_log2_buckets_micros = 2;
}