eBPF ile çekirdek telemetrisi toplama

Bu sayfada, Android'deki Genişletilmiş Berkeley Paket Filtresi (eBPF) çekirdek telemetri çerçevesine genel bir bakış sunulmakta, çerçeve mimarisi, yerleşik metrikler ve yeni çekirdek metriklerinin nasıl ekleneceği açıklanmaktadır.

Android, düşük ek yük içeren çekirdek metriklerini toplamak ve bu metrikleri statsd gibi kullanıcı alanı hizmetlerine göstermek için birleşik bir eBPF çekirdek telemetri çerçevesi içerir. Çerçeve, taşınabilir eBPF programlarını çekirdek izleme noktalarına ve kprobe'lara ekleyerek yüksek frekanslı çekirdek etkinliklerini yerinde toplar ve Genel Çekirdek Resmi'ni (GKI) değiştirmeden veya Çekirdek Modülü Arayüzü'nü (KMI) bozmadan kompakt histogramları ya da sayaçları dışa aktarır.

Geleneksel çekirdek telemetrisi yaklaşımları, çekirdek, SELinux, Java Native Interface (JNI) ve kullanıcı alanı hizmetlerinde özel tesisat gerektiren özel procfs veya sysfs düğümlerine, ağaç dışı çekirdek yamalarına ya da satıcı kancalarına dayanır. eBPF çekirdek telemetri çerçevesi, bu ardışık düzeni Android Ortak Çekirdekleri (ACK'ler) genelinde standartlaştırır ve aşağıdaki avantajları sağlar:

  • Çekirdek ABI ve KMI dalgalanmasını önler: eBPF programlarını, çekirdek kaynak kodunu değiştirmeden, çekirdek uygulama ikili arayüzünü (ABI) değiştirmeden veya TRACE_ANDROID_VH_* gibi Android tedarikçi kancaları eklemeden mevcut çekirdek izleme noktalarına ya da kprobe'lara ekler. Cihaz üreticileri ve çip üzerinde sistem (SoC) iş ortakları, yeni telemetri tedarikçi kancaları eklemek veya mevcut tedarikçi kancalarından geçiş yapmak yerine eBPF telemetrisini kullanabilir.
  • Çekirdek sürümleri arasında taşınabilirliği korur: Tek bir derlenmiş eBPF programının, android14-6.1 ve sonraki sürümler gibi GKI sürümlerinde dahili çekirdek yapısı düzenindeki değişikliklere otomatik olarak uyum sağlaması için BPF Compile Once - Run Everywhere (CO-RE) ve BPF Type Format (BTF) kullanılır.
  • Sistem sağlığı ek yükünü en aza indirir: Yüksek frekanslı çekirdek etkinliklerini doğrudan kaynakta filtreleyip toplar ve genellikle BPF haritası başına 1 KB'tan daha az olacak şekilde kompakt 32 bölümlü 2 tabanlı logaritmik (log2) histogramlara dönüştürür. Kullanıcı alanı, tek bir toplu sistem çağrısı kullanarak günde bir kez toplu harita girişlerini alır.BPF_MAP_LOOKUP_BATCH
  • SELinux ve JNI entegrasyonunu basitleştirir: Dizin düzeyinde fs_bpf_kernelstats SELinux türü devralma işlemini uygulamak için telemetri haritalarını /sys/fs/bpf/kernelstats/ altına sabitler ve yeni metrikler eklerken özel C++ ayrıştırma kodunu ortadan kaldırmak için BTF meta verilerini kullanarak JNI'da çalışma zamanında yapı üyesi ofsetlerini çözer.

Android'in eBPF programlarını ve haritalarını nasıl yükleyip sabitlediği hakkında daha fazla bilgi için Çekirdeği eBPF ile genişletme başlıklı makaleyi inceleyin.

Mimariye genel bakış

eBPF çekirdek telemetri ardışık düzeni, genel altyapı bileşenlerini metriğe özel tanımlardan ayırır. Böylece yeni bir çekirdek metriği eklemek için minimum düzeyde kod değişikliği gerekir. Aşağıdaki tabloda ardışık düzenin bileşenleri açıklanmaktadır:

BileşenArdışık düzendeki rolKapsam
BPF yükleyici, bpfloaderÇalışan çekirdekle uyumluysa önyükleme sırasında /system/etc/bpf/kernelstats/ konumundan derlenmiş eBPF programlarını yükler ve haritaları /sys/fs/bpf/kernelstats/ konumuna sabitler. Devre dışı bırakıldığında veya desteklenmediğinde yüklemeyi sorunsuz bir şekilde atlar.Genel
SELinux politikası, fs_bpf_kernelstats/sys/fs/bpf/kernelstats/ için dizin düzeyinde SELinux etiketleri uygular. Böylece system_server, metrik başına SELinux politikası güncellemeleri olmadan telemetri haritalarını okuyabilir.Genel
system/bpfprogs/telemetry/ içinde eBPF programıÇekirdek izleme noktalarına veya kproblara bağlanır, etkinlikleri filtreler ve metrikleri BPF haritalarında toplar.Metrik başına özel
JNI BTF okuyucuÇalışma zamanında derlenmiş BPF Çalıştırılabilir ve Bağlanabilir Biçim (ELF) ikilisinden BTF meta verilerini ayrıştırır, yapı üyesi ofsetlerini önbelleğe alır ve temel Java dizilerine toplu harita okumaları gerçekleştirir.Genel
statsd içinde kullanıcı alanı çekicisiGünlük olarak kümülatif histogram haritaları çeker, delta değerlerini önbelleğe alınmış geçmişe göre hesaplar ve kesin olarak yazılmış StatsEvent çekme atomları oluşturur.Atom başına tek bir case girişi olan genel yardımcı
Telemetri şemasıKesin tür atanmış statsd pull atom protobuf'u ve sunucu tarafı histogram veya değer metriği toplamalarını tanımlar.Metrik başına özel

Kullanıcı alanı tüketim modelleri

Çekirdek telemetri metriği tasarlarken, metriğin kümülatif durumu mu yoksa sıralı bir etkinlik dizisini mi temsil ettiğine bağlı olarak iki kullanıcı alanı tüketim modeli arasından seçim yapın. Aşağıdaki tabloda iki model karşılaştırılmaktadır:

ÖzellikAkümülatörlerAkışlar
TanımDurum bilgisi olan, tekdüze şekilde artan kümülatif sayaçlar veya histogramlarZaman damgalı çekirdek etkinliklerinin sıralı dizisi
Erişim rutiniİsteğe bağlı olarak kullanıcı alanı yoklamaları (ör. statsd aracılığıyla 24 saatte bir)Kullanıcı alanı, bırakılan etkinlikleri önlemek için sürekli olarak bir halka arabelleğinden tüketir.
Tipik BPF harita türleriBPF_MAP_TYPE_HASH, BPF_MAP_TYPE_LRU_HASH, BPF_MAP_TYPE_PERCPU_HASH, BPF_MAP_TYPE_LRU_PERCPU_HASHBPF_MAP_TYPE_RINGBUF
Eşzamanlılık modeliÇekirdek harita aramaları atomiktir. Birden fazla okuyucu, kullanıcı alanı kilitlemesi olmadan yoklama yapabilir.Yıkıcı okuma semantiği, tüketicilere gönderim yapan tek bir okuyucu iş parçacığı gerektirir.
Örnek kullanım alanlarıBellek yönetimi gecikme histogramları, Doğrudan Bellek Erişimi Arabelleği (DMA-BUF) ayırma boyutları, uyandırma kaynağı yineleyicileriFiltrelenmiş zamanlayıcı izleme etkinlikleri, yüksek frekanslı alt sistem güç izleri

Yerleşik çekirdek telemetri metrikleri

Android; android14-6.1, android15-6.6, android16-6.12 ve android17-6.18 dahil olmak üzere GKI 6.1 ve sonraki sürümlerde çalışan yerleşik eBPF çekirdek telemetri programları içerir. Her gecikme ve boyut metriğinde 24 saatlik bir pencerede toplanan 32 gruplu üstel (log2) histogram kullanılır. Aşağıdaki tabloda yerleşik çekirdek telemetri metrikleri listelenmiştir:

Metrik atomuAlt sistemAçıklamaBoyut anahtarı
KswapdLatencyBellek yönetimiÇekirdek takas arka plan programı, kswapd, mikrosaniye cinsinden uyanmadan uykuya geçiş yürütme gecikmesiTekdüze Olmayan Bellek Erişimi (NUMA) node_id
DirectReclaimLatencyBellek yönetimiMikrosaniye cinsinden eşzamanlı doğrudan bellek geri kazanma gecikmesiİşlem bellek dışı (OOM) oom_score_adj durumu
CompactionDurationBellek yönetimiEski sıkıştırma satıcı kancalarının yerini alan, sistem genelinde bellek sıkıştırma yürütme süresi (mikrosaniye cinsinden)Sistem genelinde
CmaAllocationLatencyBellek yönetimiBitişik Bellek Ayırıcı (CMA) sayfa ayırma gecikmesi (mikrosaniye cinsinden)CMA yığın adı
DmabufRequestSizesBellek yönetimiBayt cinsinden DMA-BUF ayırma isteği boyutlarıDışa aktarıcı yığın adı
SuspendResumeLatencyGüç yönetimiÇekirdek askıya alma ve devam ettirme aşamalarındaki gecikme (mikrosaniye cinsinden)İşlem aşamasını askıya alma

Çekirdek telemetri metriği ekleme

eBPF çekirdek telemetri işlem hattına yeni bir biriktirici metriği eklemek için aşağıdaki adımları sırasıyla tamamlayın:

  1. system/bpfprogs/telemetry/ altında eBPF programını yazın ve ikili programı /system/etc/bpf/kernelstats/ içinde derleyin.
  2. Genel JNI BTF okuyucu için BTF struct düzeni uyumluluğunu doğrulama
  3. Çekme atomunu statsd'da kaydedin.
  4. Çekme atomu protobuf şemasını tanımlayın.

Aşağıdaki bölümlerde örnek olarak DirectReclaimLatency (directReclaim.c) kullanılmıştır.

eBPF programını yazma

system/bpfprogs/telemetry/ içinde <bpf_telemetry.h> öğesini ekleyin ve eşleme anahtarı ile çıkış toplayıcı eşlemesini tanımlayın. Harita belleğinde kaplanan yeri en aza indirmek ve gizliliği korumak için BPF harita anahtarlarında ham dizeler yerine tam sayı tanımlayıcıları veya enum'ları kullanın:

#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);

İzleme noktası işleyicilerinde, girişte başlangıç zaman damgasını kaydedin, çıkışta GET_LATENCY_BUCKET kullanarak log2 gecikme aralığını hesaplayın ve INCREMENT_ACCUMULATOR ile biriktirici harita girişini artırın:

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;
}

BTF struct düzeni uyumluluğunu doğrulama

Harita anahtarınız ve değer yapılarınız standart tam sayı, enum ve sabit dizi türlerini kullandığı sürece yeni bir histogram haritası eklerken özel JNI C++ kodu yazmanız gerekmez. Genel JNI okuyucu, derleme zamanında BPF harita girişlerini sabit kodlu C yapılarına dönüştürmek yerine, yapı düzenlerini çalışma zamanında üç aşamada dinamik olarak çözer:

  • ELF-to-map ilişkilendirmesi: JNI katmanı, BTF meta verilerini doğrudan diskteki derlenmiş BPF ELF dosyasından (ör. /system/etc/bpf/kernelstats/directReclaim.bpf) libbpf btf__parse işlevini kullanarak ayrıştırır.
  • BTF türü çözünürlüğü: Okuyucu, ilk erişimde haritanın anahtar ve değer C yapılarının yapısal bir gösterimini oluşturup önbelleğe alır. Yapı üyeleri, tam sayı ve enum bayt genişlikleri ile dizi öğesi boyutları için temel işaretçiden itibaren kümülatif bayt uzaklıklarını hesaplar.
  • Toplu arama ve serileştirme: statsd haritayı çektiğinde JNI katmanı, tüm karma girişlerini toplu olarak okumak için sys_bpf sistem çağrısına tek bir BPF_MAP_LOOKUP_BATCH komutu verir ve çözümlenen anahtar kimliklerini ve paket dizilerini SetLongArrayRegion JNI işlevini kullanarak doğrudan temel Java long[] dizilerine kopyalar.

Bir BPF C yapısı gelecekteki bir sürümde değişirse yeniden derlenen BTF bölümü otomatik olarak güncellenir ve JNI okuyucu, JNI'nin yeniden derlenmesini gerektirmeden çalışma zamanında uyum sağlar.

statsd'de pull atom'u kaydetme

StatsPullAtomService.java içinde, genel pullKernelTelemetry yardımcı, sabitlenmiş BPF haritasını okur, önceki çekme işleminin önbelleğe alınmış sayılarına göre paket deltalarını hesaplar ve StatsEvent girişleri yayınlar. Yeni bir çekirdek telemetri metriği eklemek için yalnızca üç küçük ekleme yapmanız gerekir:

// 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";

Pull atom protobuf'u tanımlama

BPF eşlemesi tarafından üretilen boyut anahtarı ve 32 gruplu log2 histogram dizisiyle eşleşmesi için frameworks/proto_logging/ içinde kesin tür atanmış pull atom mesajını tanımlayın:

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;
}