Cette page présente le framework de télémétrie du noyau eBPF (Extended Berkeley Packet Filter) dans Android. Elle décrit l'architecture du framework, les métriques intégrées et comment ajouter de nouvelles métriques du noyau.
Android inclut un framework de télémétrie du noyau eBPF unifié pour collecter des métriques du noyau à faible surcharge et les exposer aux services de l'espace utilisateur tels que statsd. En associant des programmes eBPF portables à des points de trace du noyau et
à des kprobes, le framework agrège les événements du noyau à haute fréquence sur place et
exporte des histogrammes ou des compteurs compacts sans modifier l'
image générique du noyau (GKI, Generic Kernel Image) ni interrompre l'interface du module du noyau (KMI, Kernel Module Interface).
Les approches traditionnelles de la télémétrie du noyau reposent sur des nœuds procfs ou sysfs personnalisés, des correctifs du noyau hors arborescence ou des hooks de fournisseur qui nécessitent une plomberie personnalisée dans le noyau, SELinux, l'interface native Java (JNI, Java Native Interface) et les services de l'espace utilisateur.
Le framework de télémétrie du noyau eBPF standardise ce pipeline sur les noyaux communs Android (ACK, Android Common Kernels) et offre les avantages suivants :
- Évite le brassage de l'ABI et de la KMI du noyau : associe des programmes eBPF à des points de trace ou à des kprobes du noyau existants sans modifier le code source du noyau, l'interface binaire d'application (ABI, Application Binary Interface) du noyau ni ajouter de hooks de fournisseur Android tels que
TRACE_ANDROID_VH_*. Les fabricants d'appareils et les partenaires de système sur puce (SoC, System-on-Chip) peuvent utiliser la télémétrie eBPF au lieu d'ajouter de nouveaux hooks de fournisseur de télémétrie ou pour migrer à partir de hooks de fournisseur existants. - Maintient la portabilité entre les versions du noyau : utilise BPF Compile Once - Run Everywhere (CO-RE) et BPF Type Format (BTF) afin qu'un seul programme eBPF compilé s'adapte automatiquement aux modifications de la mise en page de la structure interne du noyau entre les versions de GKI, telles que
android14-6.1et versions ultérieures. - Réduit la surcharge de l'état du système : filtre et agrège les événements du noyau à haute fréquence directement à la source dans des histogrammes logarithmiques de base 2 (
log2) compacts à 32 buckets, généralement inférieurs à 1 Ko par carte BPF. L'espace utilisateur récupère les entrées de carte agrégées une fois par jour à l'aide d'un seul appel système par lotBPF_MAP_LOOKUP_BATCH. - Simplifie l'intégration de SELinux et de JNI : épingle les cartes de télémétrie sous
/sys/fs/bpf/kernelstats/pour appliquer l'héritage de type SELinuxfs_bpf_kernelstatsau niveau du répertoire et résout les décalages des membres de la structure au moment de l'exécution dans JNI à l'aide des métadonnées BTF pour éliminer le code d'analyse C++ personnalisé lors de l'ajout de nouvelles métriques.
Pour en savoir plus sur la façon dont Android charge et épingle les programmes et les cartes eBPF, consultez Étendre le noyau avec eBPF.
Présentation de l'architecture
Le pipeline de télémétrie du noyau eBPF sépare les composants d'infrastructure génériques des définitions spécifiques aux métriques. L'ajout d'une nouvelle métrique du noyau nécessite donc des modifications minimales du code. Le tableau suivant décrit les composants du pipeline :
| Composant | Rôle dans le pipeline | Champ d'application |
|---|---|---|
Chargeur BPF, bpfloader | Charge les programmes eBPF compilés à partir de /system/etc/bpf/kernelstats/ au démarrage s'ils sont compatibles avec le noyau en cours d'exécution et épingle les cartes à /sys/fs/bpf/kernelstats/. Ignore le chargement proprement lorsqu'il est désactivé ou non compatible. | Générique |
Règles SELinux, fs_bpf_kernelstats | Applique des libellés SELinux au niveau du répertoire à /sys/fs/bpf/kernelstats/ afin que system_server puisse lire les cartes de télémétrie sans mises à jour des règles SELinux par métrique. | Générique |
Programme eBPF dans system/bpfprogs/telemetry/ | S'associe aux points de trace ou aux kprobes du noyau, filtre les événements et agrège les métriques dans les cartes BPF. | Personnalisé par métrique |
| Lecteur BTF JNI | Analyse les métadonnées BTF à partir du binaire ELF (Executable and Linkable Format) BPF compilé au moment de l'exécution, met en cache les décalages des membres de la structure et effectue des lectures de cartes par lot dans des tableaux Java primitifs. | Générique |
Extracteur d'espace utilisateur dans statsd | Extrait les cartes d'histogramme cumulatives selon une programmation quotidienne, calcule les deltas par rapport à l'historique mis en cache et crée des atomes d'extraction StatsEvent fortement typés. | Assistant générique avec une seule entrée case par atome |
| Schéma de télémétrie | Définit le protobuf d'atome d'extraction statsd fortement typé et les agrégations de métriques d'histogramme ou de valeur côté serveur. | Personnalisé par métrique |
Modèles de consommation de l'espace utilisateur
Lorsque vous concevez une métrique de télémétrie du noyau, choisissez entre deux modèles de consommation de l'espace utilisateur selon que la métrique représente un état cumulatif ou une séquence d'événements ordonnée. Le tableau suivant compare les deux modèles :
| Propriété | Accumulateurs | Flux |
|---|---|---|
| Définition | Compteurs ou histogrammes cumulatifs avec état et à augmentation monotone | Séquence ordonnée d'événements du noyau horodatés |
| Modèle d'accès | L'espace utilisateur interroge à la demande, par exemple une fois toutes les 24 heures via statsd | L'espace utilisateur consomme en continu à partir d'un tampon circulaire pour éviter les événements supprimés |
| Types de cartes BPF typiques | BPF_MAP_TYPE_HASH, BPF_MAP_TYPE_LRU_HASH, BPF_MAP_TYPE_PERCPU_HASH, BPF_MAP_TYPE_LRU_PERCPU_HASH | BPF_MAP_TYPE_RINGBUF |
| Modèle de simultanéité | Les recherches de cartes du noyau sont atomiques. Plusieurs lecteurs peuvent interroger sans verrouillage de l'espace utilisateur | La sémantique de lecture destructive nécessite un seul thread de lecteur qui distribue aux consommateurs |
| Exemples de cas d'utilisation | Histogrammes de latence de gestion de la mémoire, tailles d'allocation de tampon d'accès direct à la mémoire (DMA-BUF, Direct Memory Access Buffer), itérateurs de source de réveil | Événements de trace de planificateur filtrés, traces d'alimentation de sous-système à haute fréquence |
Métriques de télémétrie du noyau intégrées
Android inclut des programmes de télémétrie du noyau eBPF intégrés qui fonctionnent sur les noyaux GKI 6.1 et versions ultérieures, y compris android14-6.1, android15-6.6, android16-6.12 et android17-6.18. Chaque métrique de latence et de taille utilise un histogramme exponentiel (log2) à 32 buckets agrégé sur une période de 24 heures. Le tableau suivant répertorie les métriques de télémétrie du noyau intégrées :
| Atome de métrique | Sous-système | Description | Clé de dimension |
|---|---|---|---|
KswapdLatency | Gestion de la mémoire | Daemon de remplacement du noyau, kswapd, latence d'exécution de la mise en veille en microsecondes | Accès mémoire non uniforme (NUMA, Non-Uniform Memory Access) node_id |
DirectReclaimLatency | Gestion de la mémoire | Latence de récupération directe de la mémoire synchrone en microsecondes | État oom_score_adj de mémoire insuffisante (OOM, Out-Of-Memory) du processus |
CompactionDuration | Gestion de la mémoire | Durée d'exécution de la compaction de la mémoire à l'échelle du système en microsecondes, remplaçant les hooks de fournisseur de compaction hérités | À l'échelle du système |
CmaAllocationLatency | Gestion de la mémoire | Latence d'allocation de page de l'allocateur de mémoire contiguë (CMA, Contiguous Memory Allocator) en microsecondes | Nom du tas CMA |
DmabufRequestSizes | Gestion de la mémoire | Tailles des requêtes d'allocation DMA-BUF en octets | Nom du tas de l'exportateur |
SuspendResumeLatency | Gestion de l'alimentation | Latence entre les étapes de suspension et de reprise du noyau en microsecondes | Étape de l'action de suspension |
Ajouter une métrique de télémétrie du noyau
Pour ajouter une nouvelle métrique d'accumulateur au pipeline de télémétrie du noyau eBPF, procédez comme suit dans l'ordre :
- Écrivez le programme eBPF sous
system/bpfprogs/telemetry/et compilez le binaire dans/system/etc/bpf/kernelstats/. - Vérifiez la compatibilité de la mise en page de la structure BTF pour le lecteur BTF JNI générique.
- Enregistrez l'atome d'extraction dans
statsd. - Définissez le schéma protobuf de l'atome d'extraction.
Les sections suivantes utilisent DirectReclaimLatency (directReclaim.c) comme exemple.
Écrire le programme eBPF
Dans system/bpfprogs/telemetry/, incluez <bpf_telemetry.h> et définissez la clé de carte
et la carte d'accumulateur de sortie. Utilisez des identifiants entiers ou des énumérations dans les clés de carte BPF plutôt que des chaînes brutes pour minimiser l'espace mémoire utilisé de la carte et préserver la confidentialité :
#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);
Dans les gestionnaires de points de trace, enregistrez l'horodatage de début à l'entrée, calculez le bucket de latence log2 à la sortie à l'aide de GET_LATENCY_BUCKET et incrémentez l'entrée de carte d'accumulateur avec 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;
}
Vérifier la compatibilité de la mise en page de la structure BTF
Vous n'avez pas besoin d'écrire de code C++ JNI personnalisé lorsque vous ajoutez une nouvelle carte d'histogramme, à condition que vos structures de clé et de valeur de carte utilisent des types entiers, d'énumération et de tableau fixe standards. Au lieu de caster les entrées de carte BPF dans des structures C codées en dur au temps de compilation, le lecteur JNI générique résout les mises en page de la structure de manière dynamique au moment de l'exécution en trois phases :
- Association ELF-carte : la couche JNI analyse les métadonnées BTF directement à partir du fichier ELF BPF compilé sur le disque, tel que
/system/etc/bpf/kernelstats/directReclaim.bpf, à l'aide de la fonctionlibbpfbtf__parse. - Résolution du type BTF : lors du premier accès, le lecteur crée et met en cache une représentation structurelle des structures C de clé et de valeur de la carte, en calculant les décalages d'octets cumulatifs pour les membres de la structure, les largeurs d'octets entiers et d'énumération, et les tailles des éléments de tableau à partir du pointeur de base.
- Recherche et sérialisation par lot : lorsque
statsdextrait la carte, la couche JNI émet une seule commandeBPF_MAP_LOOKUP_BATCHvers l'appel systèmesys_bpfpour lire toutes les entrées de hachage en bloc et copie les ID de clé résolus et les tableaux de buckets directement dans des tableauxlong[]Java primitifs à l'aide de la fonction JNISetLongArrayRegion.
Si une structure C BPF change dans une version ultérieure, la section BTF recompilée est mise à jour automatiquement et le lecteur JNI s'adapte au moment de l'exécution sans nécessiter de recompilation JNI.
Enregistrer l'atome d'extraction dans statsd
Dans StatsPullAtomService.java, l'assistant générique pullKernelTelemetry lit la carte BPF épinglée, calcule les deltas de bucket par rapport aux nombres mis en cache de l'extraction précédente et émet des entrées StatsEvent. L'ajout d'une nouvelle métrique de télémétrie du noyau ne nécessite que trois petites additions :
// 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";
Définir le protobuf de l'atome d'extraction
Définissez le message d'atome d'extraction fortement typé dans frameworks/proto_logging/ pour qu'il corresponde à la clé de dimension et au tableau d'histogramme log2 à 32 buckets produit par la carte 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;
}