Esta página oferece uma visão geral da estrutura de telemetria do kernel do Extended Berkeley Packet Filter (eBPF) no Android, descrevendo a arquitetura da estrutura, as métricas integradas e como adicionar novas métricas do kernel.
O Android inclui uma estrutura unificada de telemetria do kernel do eBPF para coletar métricas de kernel de baixa sobrecarga e expor essas métricas a serviços do espaço do usuário, como statsd. Ao anexar programas portáteis do eBPF a tracepoints e
kprobes do kernel, a estrutura agrega eventos de kernel de alta frequência no local e
exporta histogramas ou contadores compactos sem modificar a
imagem genérica do kernel (GKI, na sigla em inglês) ou interromper a interface do módulo do kernel (KMI, na sigla em inglês).
As abordagens tradicionais da telemetria do kernel dependem de nós procfs ou sysfs personalizados, patches de kernel fora da árvore ou hooks do fornecedor que exigem encanamento personalizado no kernel, SELinux, Java Native Interface (JNI) e serviços do espaço do usuário.
A estrutura de telemetria do kernel do eBPF padroniza esse pipeline em kernels comuns do Android (ACKs, na sigla em inglês) e oferece os seguintes benefícios:
- Evita a rotatividade da ABI e da KMI do kernel:anexa programas do eBPF a tracepoints ou kprobes do kernel sem modificar o código-fonte do kernel, mudar a interface binária do aplicativo do kernel (ABI, na sigla em inglês) ou adicionar hooks do fornecedor do Android, como
TRACE_ANDROID_VH_*. Os fabricantes de dispositivos e os parceiros de sistema em chip (SoC, na sigla em inglês) podem usar a telemetria do eBPF em vez de adicionar novos hooks do fornecedor de telemetria ou migrar hooks do fornecedor atuais. - Mantém a portabilidade entre versões do kernel:usa o BPF Compile Once - Run Everywhere (CO-RE) e o BPF Type Format (BTF) para que um único programa eBPF compilado se adapte automaticamente às mudanças internas de layout de struct do kernel em versões da GKI, como
android14-6.1e mais recentes. - Minimiza a sobrecarga de integridade do sistema:filtra e agrega eventos de kernel de alta frequência diretamente na origem em histogramas logarítmicos de base 2 (
log2) de 32 buckets compactos, normalmente com menos de 1 KB por mapa BPF. O espaço do usuário recupera entradas de mapa agregadas uma vez por dia usando uma única syscall de loteBPF_MAP_LOOKUP_BATCH. - Simplifica a integração do SELinux e da JNI:fixa mapas de telemetria em
/sys/fs/bpf/kernelstats/para aplicar a herança de tipofs_bpf_kernelstatsdo SELinux no nível do diretório e resolve deslocamentos de membros de struct no tempo de execução na JNI usando metadados BTF para eliminar o código de análise C++ personalizado ao adicionar novas métricas.
Para mais informações sobre como o Android carrega e fixa programas e mapas do eBPF, consulte Estender o kernel com o eBPF.
Visão geral da arquitetura
O pipeline de telemetria do kernel do eBPF separa os componentes de infraestrutura genéricos das definições específicas da métrica para que a adição de uma nova métrica do kernel exija mudanças mínimas de código. A tabela a seguir descreve os componentes do pipeline:
| Componente | Função no pipeline | Escopo |
|---|---|---|
Carregador de BPF, bpfloader | Carrega programas eBPF compilados de /system/etc/bpf/kernelstats/ na inicialização, se compatível com o kernel em execução, e fixa mapas em /sys/fs/bpf/kernelstats/. Ignora o carregamento de forma limpa quando desativado ou sem suporte. | Genérico |
Política do SELinux, fs_bpf_kernelstats | Aplica rótulos do SELinux no nível do diretório a /sys/fs/bpf/kernelstats/ para que system_server possa ler mapas de telemetria sem atualizações de política do SELinux por métrica. | Genérico |
Programa eBPF em system/bpfprogs/telemetry/ | Anexa a tracepoints ou kprobes do kernel, filtra eventos e agrega métricas em mapas BPF. | Personalizado por métrica |
| Leitor de BTF da JNI | Analisa metadados BTF do binário compilado do formato executável e vinculável (ELF, na sigla em inglês) do BPF no tempo de execução, armazena em cache os deslocamentos de membros de struct e realiza leituras de mapa em lote em matrizes primitivas Java. | Genérico |
Extrator do espaço do usuário em statsd | Extrai mapas de histograma cumulativos em uma programação diária, calcula deltas em relação ao histórico armazenado em cache e cria átomos de extração StatsEvent fortemente tipados. | Assistente genérico com uma única entrada case por átomo |
| Esquema de telemetria | Define o protobuf de átomo de extração statsd fortemente tipado e agregações de métricas de histograma ou valor do lado do servidor. | Personalizado por métrica |
Modelos de consumo do espaço do usuário
Ao criar uma métrica de telemetria do kernel, escolha entre dois modelos de consumo do espaço do usuário, dependendo se a métrica representa um estado cumulativo ou uma sequência de eventos ordenada. A tabela a seguir compara os dois modelos:
| Propriedade | Acumuladores | Streams |
|---|---|---|
| Definição | Contadores ou histogramas cumulativos com estado e monotonicamente crescentes | Sequência ordenada de eventos do kernel com carimbo de data/hora |
| Padrão de acesso | O espaço do usuário pesquisa sob demanda, como uma vez a cada 24 horas pelo statsd | O espaço do usuário consome continuamente de um buffer de anel para evitar eventos descartados |
| Tipos de mapa 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 simultaneidade | As pesquisas de mapa do kernel são atômicas. Vários leitores podem pesquisar sem bloqueio do espaço do usuário | A semântica de leitura destrutiva exige um único encadeamento de leitor que envia para os consumidores |
| Exemplos de casos de uso | Histogramas de latência de gerenciamento de memória, tamanhos de alocação de buffer de acesso direto à memória (DMA-BUF, na sigla em inglês), iteradores de origem de ativação | Eventos de trace do agendador filtrados, traces de energia de subsistema de alta frequência |
Métricas de telemetria do kernel integradas
O Android inclui programas de telemetria do kernel do eBPF integrados que funcionam em kernels GKI 6.1 e mais recentes, incluindo android14-6.1, android15-6.6, android16-6.12 e android17-6.18. Cada métrica de latência e tamanho usa um histograma exponencial (log2) de 32 buckets agregado em uma janela de 24 horas. A tabela a seguir lista as métricas de telemetria do kernel integradas:
| Átomo métrico | Subsistema | Descrição | Chave de dimensão |
|---|---|---|---|
KswapdLatency | Gerenciamento de memória | Latência de execução de ativação para suspensão do daemon de troca de kernel, kswapd, em microssegundos | node_id de acesso à memória não uniforme (NUMA, na sigla em inglês) |
DirectReclaimLatency | Gerenciamento de memória | Latência de recuperação de memória direta síncrona em microssegundos | Estado oom_score_adj de falta de memória (OOM, na sigla em inglês) do processo |
CompactionDuration | Gerenciamento de memória | Duração da execução da compactação de memória em todo o sistema em microssegundos, substituindo hooks de fornecedor de compactação legados | Em todo o sistema |
CmaAllocationLatency | Gerenciamento de memória | Latência de alocação de página do alocador de memória contígua (CMA, na sigla em inglês) em microssegundos | Nome do heap CMA |
DmabufRequestSizes | Gerenciamento de memória | Tamanhos de solicitação de alocação de DMA-BUF em bytes | Nome do heap do exportador |
SuspendResumeLatency | Gerenciamento de energia | Latência nas fases de suspensão e retomada do kernel em microssegundos | Fase de ação de suspensão |
Adicionar uma métrica de telemetria do kernel
Para adicionar uma nova métrica de acumulador ao pipeline de telemetria do kernel do eBPF, siga estas etapas em ordem:
- Grave o programa eBPF em
system/bpfprogs/telemetry/e crie o binário em/system/etc/bpf/kernelstats/. - Verifique a compatibilidade do layout de struct BTF para o leitor de BTF da JNI genérico.
- Registre o átomo de extração em
statsd. - Defina o esquema de protobuf do átomo de extração.
As seções a seguir usam DirectReclaimLatency (directReclaim.c) como exemplo.
Gravar o programa eBPF
Em system/bpfprogs/telemetry/, inclua <bpf_telemetry.h> e defina a chave do mapa
e o mapa do acumulador de saída. Use identificadores de números inteiros ou enums em chaves de mapa BPF em vez de strings brutas para minimizar o consumo de memória do mapa e preservar a privacidade:
#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);
Nos gerenciadores de tracepoint, registre o carimbo de data/hora de início na entrada, calcule o bucket de latência log2 na saída usando GET_LATENCY_BUCKET e incremente a entrada do mapa do acumulador com 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;
}
Verificar a compatibilidade do layout de struct BTF
Não é necessário gravar código C++ da JNI personalizado ao adicionar um novo mapa de histograma, desde que a chave do mapa e as structs de valor usem tipos de matriz de números inteiros, enum e fixos padrão. Em vez de converter entradas de mapa BPF em structs C fixadas no código no tempo de compilação, o leitor de JNI genérico resolve layouts de struct dinamicamente no tempo de execução em três fases:
- Associação de ELF a mapa:a camada JNI analisa metadados BTF diretamente do arquivo ELF do BPF compilado no disco, como
/system/etc/bpf/kernelstats/directReclaim.bpf, usando a funçãobtf__parselibbpf. - Resolução de tipo BTF:no primeiro acesso, o leitor cria e armazena em cache uma representação estrutural das structs C de chave e valor do mapa, calculando deslocamentos de bytes cumulativos para membros de struct, larguras de bytes de números inteiros e enum e tamanhos de elementos de matriz do ponteiro base.
- Pesquisa e serialização em lote:quando
statsdextrai o mapa, a camada JNI emite um único comandoBPF_MAP_LOOKUP_BATCHpara a syscallsys_bpfpara ler todas as entradas de hash em massa e copia os IDs de chave resolvidos e as matrizes de bucket diretamente para matrizeslong[]Java primitivas usando a funçãoSetLongArrayRegionda JNI.
Se uma struct C do BPF mudar em uma versão futura, a seção BTF recompilada será atualizada automaticamente e o leitor de JNI será adaptado no tempo de execução sem exigir a recompilação da JNI.
Registrar o átomo de extração no statsd
Em StatsPullAtomService.java, o auxiliar genérico pullKernelTelemetry lê o mapa BPF fixado, calcula deltas de bucket em relação às contagens armazenadas em cache da extração anterior e emite entradas StatsEvent. A adição de uma nova métrica de telemetria do kernel exige apenas três pequenas adições:
// 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";
Definir o protobuf do átomo de extração
Defina a mensagem de átomo de extração fortemente tipada em frameworks/proto_logging/ para corresponder à chave de dimensão e à matriz de histograma log2 de 32 buckets produzida pelo mapa 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;
}