Questa pagina fornisce una panoramica del framework di telemetria del kernel Extended Berkeley Packet Filter (eBPF) in Android, descrivendo l'architettura del framework, le metriche integrate e come aggiungere nuove metriche del kernel.
Android include un framework di telemetria del kernel eBPF unificato per la raccolta di metriche del kernel a basso overhead e l'esposizione di queste metriche ai servizi dello spazio utente come statsd. Collegando i programmi eBPF portatili ai tracepoint e ai kprobe del kernel, il framework aggrega gli eventi del kernel ad alta frequenza in loco ed esporta istogrammi o contatori compatti senza modificare l'immagine del kernel generico (GKI) o interrompere l'interfaccia del modulo kernel (KMI).
Gli approcci tradizionali alla telemetria del kernel si basano su nodi procfs o sysfs personalizzati, patch del kernel out-of-tree o hook del fornitore che richiedono un plumbing personalizzato tra kernel, SELinux, Java Native Interface (JNI) e servizi dello spazio utente.
Il framework di telemetria del kernel eBPF standardizza questa pipeline su tutti i kernel comuni di Android (ACK) e offre i seguenti vantaggi:
- Evita il churn dell'ABI del kernel e del KMI: collega i programmi eBPF ai tracepoint o ai kprobe del kernel esistenti senza modificare il codice sorgente del kernel, cambiare l'interfaccia binaria dell'applicazione del kernel (ABI) o aggiungere hook del fornitore Android come
TRACE_ANDROID_VH_*. I produttori di dispositivi e i partner di system-on-chip (SoC) possono utilizzare la telemetria eBPF anziché aggiungere nuovi hook del fornitore di telemetria o per eseguire la migrazione dagli hook del fornitore esistenti. - Mantiene la portabilità tra le versioni kernel: utilizza BPF Compile Once - Run Everywhere (CO-RE) e BPF Type Format (BTF) in modo che un singolo programma eBPF compilato si adatti automaticamente alle modifiche del layout della struttura interna del kernel tra le versioni GKI, come
android14-6.1e versioni successive. - Riduce al minimo l'overhead di integrità del sistema: filtra e aggrega gli eventi del kernel ad alta frequenza direttamente all'origine in istogrammi logaritmici (
log2) base 2 compatti a 32 bucket, in genere inferiori a 1 KB per mappa BPF. Lo spazio utente recupera le voci della mappa aggregate una volta al giorno utilizzando una singola chiamata di sistema batchBPF_MAP_LOOKUP_BATCH. - Semplifica l'integrazione di SELinux e JNI: fissa le mappe di telemetria in
/sys/fs/bpf/kernelstats/per applicare l'ereditarietà del tipo SELinuxfs_bpf_kernelstatsa livello di directory e risolve gli offset dei membri della struttura in fase di runtime in JNI utilizzando i metadati BTF per eliminare il codice di analisi C++ personalizzato quando si aggiungono nuove metriche.
Per saperne di più su come Android carica e fissa i programmi eBPF e le mappe, consulta Estendere il kernel con eBPF.
Panoramica dell'architettura
La pipeline di telemetria del kernel eBPF separa i componenti dell'infrastruttura generica dalle definizioni specifiche delle metriche, in modo che l'aggiunta di una nuova metrica del kernel richieda modifiche minime al codice. La seguente tabella descrive i componenti della pipeline:
| Componente | Ruolo nella pipeline | Ambito |
|---|---|---|
Caricatore BPF, bpfloader | Carica i programmi eBPF compilati da /system/etc/bpf/kernelstats/ all'avvio se compatibili con il kernel in esecuzione e fissa le mappe a /sys/fs/bpf/kernelstats/. Salta il caricamento in modo pulito quando è disabilitato o non supportato. | Generica |
Policy SELinux, fs_bpf_kernelstats | Applica le etichette SELinux a livello di directory a /sys/fs/bpf/kernelstats/ in modo che system_server possa leggere le mappe di telemetria senza aggiornamenti delle policy SELinux per metrica. | Generica |
Programma eBPF in system/bpfprogs/telemetry/ | Si collega ai tracepoint o ai kprobe del kernel, filtra gli eventi e aggrega le metriche nelle mappe BPF. | Personalizzata per metrica |
| Lettore BTF JNI | Analizza i metadati BTF dal file binario ELF (Executable and Linkable Format) BPF compilato in fase di runtime, memorizza nella cache gli offset dei membri della struttura ed esegue letture batch della mappa in array Java primitivi. | Generica |
Puller dello spazio utente in statsd | Estrae le mappe degli istogrammi cumulativi in base a una pianificazione giornaliera, calcola le differenze rispetto alla cronologia memorizzata nella cache e crea atomi di pull StatsEvent con tipo di dati definito. | Helper generico con una singola voce case per atomo |
| Schema di telemetria | Definisce il protobuf dell'atomo di pull statsd con tipo di dati definito e le aggregazioni di istogrammi o metriche di valore lato server. | Personalizzata per metrica |
Modelli di consumo dello spazio utente
Quando progetti una metrica di telemetria del kernel, scegli tra due modelli di consumo dello spazio utente a seconda che la metrica rappresenti uno stato cumulativo o una sequenza di eventi ordinata. La seguente tabella confronta i due modelli:
| Proprietà | Accumulatori | Stream |
|---|---|---|
| Definizione | Contatori o istogrammi cumulativi con stato e aumento monotono | Sequenza ordinata di eventi del kernel con timestamp |
| Pattern di accesso | Lo spazio utente esegue il polling on demand, ad esempio una volta ogni 24 ore tramite statsd | Lo spazio utente utilizza continuamente un buffer circolare per evitare eventi eliminati |
| Tipi di mappe BPF tipici | BPF_MAP_TYPE_HASH, BPF_MAP_TYPE_LRU_HASH, BPF_MAP_TYPE_PERCPU_HASH, BPF_MAP_TYPE_LRU_PERCPU_HASH | BPF_MAP_TYPE_RINGBUF |
| Modello di concorrenza | Le ricerche di mappe del kernel sono atomiche; più lettori possono eseguire il polling senza bloccare lo spazio utente | La semantica di lettura distruttiva richiede un singolo thread di lettura che invia ai consumatori |
| Esempi di casi d'uso | Istogrammi di latenza di gestione della memoria, dimensioni di allocazione del buffer di accesso diretto alla memoria (DMA-BUF), iteratori di origine di riattivazione | Eventi di traccia dello scheduler filtrati, tracce di alimentazione del sottosistema ad alta frequenza |
Metriche di telemetria del kernel integrate
Android include programmi di telemetria del kernel eBPF integrati che funzionano su kernel GKI 6.1 e versioni successive, tra cui android14-6.1, android15-6.6, android16-6.12 e android17-6.18. Ogni metrica di latenza e dimensione utilizza un istogramma esponenziale (log2) a 32 bucket aggregato su una finestra di 24 ore. La seguente tabella elenca le metriche di telemetria del kernel integrate:
| Atomo di metrica | OOB | Descrizione | Chiave di dimensione |
|---|---|---|---|
KswapdLatency | Gestione della memoria | Latenza di esecuzione di wake-to-sleep del daemon di swap del kernel, kswapd, in microsecondi | node_id di accesso alla memoria non uniforme (NUMA) |
DirectReclaimLatency | Gestione della memoria | Latenza di recupero della memoria diretta sincrona in microsecondi | Stato oom_score_adj di esaurimento della memoria (OOM) del processo |
CompactionDuration | Gestione della memoria | Durata di esecuzione della compattazione della memoria a livello di sistema in microsecondi, in sostituzione degli hook del fornitore di compattazione legacy | A livello di sistema |
CmaAllocationLatency | Gestione della memoria | Latenza di allocazione delle pagine di Contiguous Memory Allocator (CMA) in microsecondi | Nome heap CMA |
DmabufRequestSizes | Gestione della memoria | Dimensioni delle richieste di allocazione DMA-BUF in byte | Nome heap esportatore |
SuspendResumeLatency | Gestione alimentazione | Latenza tra le fasi di sospensione e ripresa del kernel in microsecondi | Fase di azione di sospensione |
Aggiungere una metrica di telemetria del kernel
Per aggiungere una nuova metrica di accumulatore alla pipeline di telemetria del kernel eBPF, completa i seguenti passaggi in ordine:
- Scrivi il programma eBPF in
system/bpfprogs/telemetry/e crea il file binario in/system/etc/bpf/kernelstats/. - Verifica la compatibilità del layout della struttura BTF per il lettore BTF JNI generico.
- Registra l'atomo di pull in
statsd. - Definisci lo schema protobuf dell'atomo di pull.
Le seguenti sezioni utilizzano DirectReclaimLatency (directReclaim.c) come esempio.
Scrivere il programma eBPF
In system/bpfprogs/telemetry/, includi <bpf_telemetry.h> e definisci la chiave della mappa
e la mappa dell'accumulatore di output. Utilizza identificatori interi o enum nelle chiavi della mappa BPF anziché stringhe non elaborate per ridurre al minimo il footprint della memoria della mappa e preservare la privacy:
#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);
Nei gestori dei tracepoint, registra il timestamp di inizio all'ingresso, calcola il bucket di latenza log2 all'uscita utilizzando GET_LATENCY_BUCKET e incrementa la voce della mappa dell'accumulatore 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;
}
Verificare la compatibilità del layout della struttura BTF
Non è necessario scrivere codice JNI C++ personalizzato quando aggiungi una nuova mappa di istogrammi, a condizione che le strutture della chiave e del valore della mappa utilizzino tipi di array interi, enum e fissi standard. Anziché eseguire il cast delle voci della mappa BPF a strutture C hardcoded in fase di compilazione, il lettore JNI generico risolve i layout delle strutture in modo dinamico in fase di runtime in tre fasi:
- Associazione ELF-mappa: il livello JNI analizza i metadati BTF direttamente dal file ELF BPF compilato su disco, ad esempio
/system/etc/bpf/kernelstats/directReclaim.bpf, utilizzando la funzionebtf__parsedilibbpf. - Risoluzione del tipo BTF: al primo accesso, il lettore crea e memorizza nella cache una rappresentazione strutturale delle strutture C della chiave e del valore della mappa, calcolando gli offset di byte cumulativi per i membri della struttura, le larghezze di byte interi ed enum e le dimensioni degli elementi dell'array dal puntatore di base.
- Ricerca e serializzazione batch: quando
statsdestrae la mappa, il livello JNI emette un singolo comandoBPF_MAP_LOOKUP_BATCHalla chiamata di sistemasys_bpfper leggere tutte le voci hash in blocco e copia gli ID delle chiavi risolte e gli array di bucket direttamente negli arraylong[]Java primitivi utilizzando la funzione JNISetLongArrayRegion.
Se una struttura BPF C cambia in una release futura, la sezione BTF ricompilata si aggiorna automaticamente e il lettore JNI si adatta in fase di runtime senza richiedere la ricompilazione JNI.
Registrare l'atomo di pull in statsd
In StatsPullAtomService.java, l'helper generico pullKernelTelemetry legge la mappa BPF fissa, calcola le differenze dei bucket rispetto ai conteggi memorizzati nella cache dell'estrazione precedente ed emette voci StatsEvent. L'aggiunta di una nuova metrica di telemetria del kernel richiede solo tre piccole aggiunte:
// 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";
Definire il protobuf dell'atomo di pull
Definisci il messaggio dell'atomo di pull con tipo di dati definito in frameworks/proto_logging/ in modo che corrisponda alla chiave di dimensione e all'array di istogrammi log2 a 32 bucket prodotto dalla mappa 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;
}