Mengumpulkan telemetri kernel dengan eBPF

Halaman ini memberikan ringkasan tentang framework telemetri kernel Extended Berkeley Packet Filter (eBPF) di Android, yang menjelaskan arsitektur framework, metrik bawaan, dan cara menambahkan metrik kernel baru.

Android menyertakan framework telemetri kernel eBPF terpadu untuk mengumpulkan metrik kernel overhead rendah dan mengekspos metrik tersebut ke layanan ruang pengguna seperti statsd. Dengan melampirkan program eBPF portabel ke tracepoint kernel dan kprobe, framework ini menggabungkan peristiwa kernel frekuensi tinggi di tempat dan mengekspor histogram atau penghitung ringkas tanpa mengubah Generic Kernel Image (GKI) atau merusak Kernel Module Interface (KMI).

Pendekatan tradisional untuk telemetri kernel mengandalkan node procfs atau sysfs kustom, patch kernel di luar pohon, atau hook vendor yang memerlukan plumbing kustom di seluruh kernel, SELinux, Java Native Interface (JNI), dan layanan ruang pengguna. Framework telemetri kernel eBPF menstandarkan pipeline ini di seluruh Android Common Kernels (ACK) dan memberikan manfaat berikut:

  • Menghindari churn KMI dan ABI kernel: Melampirkan program eBPF ke tracepoint kernel atau kprobe yang ada tanpa mengubah kode sumber kernel, mengubah antarmuka biner aplikasi (ABI) kernel, atau menambahkan hook vendor Android seperti TRACE_ANDROID_VH_*. Produsen perangkat dan partner system-on-chip (SoC) dapat menggunakan telemetri eBPF, bukan menambahkan hook vendor telemetri baru atau untuk bermigrasi dari hook vendor yang ada.
  • Mempertahankan portabilitas di seluruh versi kernel: Menggunakan BPF Compile Once - Run Everywhere (CO-RE) dan BPF Type Format (BTF) sehingga satu program eBPF yang dikompilasi akan otomatis beradaptasi dengan perubahan tata letak struct kernel internal di seluruh versi GKI, seperti android14-6.1 dan yang lebih tinggi.
  • Meminimalkan overhead kesehatan sistem: Memfilter dan menggabungkan peristiwa kernel frekuensi tinggi langsung di sumber ke dalam histogram logaritmik (log2) basis-2 32 bucket yang ringkas, biasanya di bawah 1 KB per peta BPF. Ruang pengguna mengambil entri peta gabungan sekali sehari menggunakan satu syscall batch BPF_MAP_LOOKUP_BATCH.
  • Menyederhanakan integrasi SELinux dan JNI: Menyematkan peta telemetri di bawah /sys/fs/bpf/kernelstats/ untuk menerapkan pewarisan jenis SELinux fs_bpf_kernelstats tingkat direktori, dan menyelesaikan offset anggota struct saat runtime di JNI menggunakan metadata BTF untuk menghilangkan kode penguraian C++ kustom saat menambahkan metrik baru.

Untuk mengetahui informasi selengkapnya tentang cara Android memuat dan menyematkan program dan peta eBPF, lihat Memperluas kernel dengan eBPF.

Ringkasan arsitektur

Pipeline telemetri kernel eBPF memisahkan komponen infrastruktur generik dari definisi khusus metrik sehingga penambahan metrik kernel baru hanya memerlukan perubahan kode minimal. Tabel berikut menjelaskan komponen pipeline:

KomponenPeran dalam pipelineCakupan
Loader BPF, bpfloaderMemuat program eBPF yang dikompilasi dari /system/etc/bpf/kernelstats/ saat booting jika kompatibel dengan kernel yang berjalan dan menyematkan peta ke /sys/fs/bpf/kernelstats/. Melewatkan pemuatan dengan bersih saat dinonaktifkan atau tidak didukung.Generik
Kebijakan SELinux, fs_bpf_kernelstatsMenerapkan label SELinux tingkat direktori ke /sys/fs/bpf/kernelstats/ sehingga system_server dapat membaca peta telemetri tanpa pembaruan kebijakan SELinux per metrik.Generik
Program eBPF di system/bpfprogs/telemetry/Dilampirkan ke tracepoint kernel atau kprobe, memfilter peristiwa, dan menggabungkan metrik ke dalam peta BPF.Kustom per metrik
Pembaca BTF JNIMengurai metadata BTF dari biner BPF Executable and Linkable Format (ELF) yang dikompilasi saat runtime, menyimpan offset anggota struct dalam cache, dan melakukan pembacaan peta batch ke dalam array Java primitif.Generik
Pengambil ruang pengguna di statsdMengambil peta histogram kumulatif sesuai jadwal harian, menghitung delta terhadap histori yang di-cache, dan membuat atom pull StatsEvent yang diketik dengan kuat.Helper generik dengan satu entri case per atom
Skema telemetriMenentukan protobuf atom pull statsd yang diketik dengan kuat dan agregasi metrik nilai atau histogram sisi server.Kustom per metrik

Model pemakaian ruang pengguna

Saat mendesain metrik telemetri kernel, pilih antara dua model konsumsi ruang pengguna, bergantung pada apakah metrik tersebut mewakili status kumulatif atau urutan peristiwa yang diurutkan. Tabel berikut membandingkan kedua model tersebut:

PropertiAkumulatorStreaming
DefinisiPenghitung atau histogram kumulatif yang stateful dan meningkat secara monotonUrutan peristiwa kernel yang diberi stempel waktu
Pola aksesPolling ruang pengguna sesuai permintaan, seperti sekali setiap 24 jam melalui statsdRuang pengguna terus menggunakan dari ring buffer untuk menghindari peristiwa yang dihilangkan
Jenis peta BPF umumBPF_MAP_TYPE_HASH, BPF_MAP_TYPE_LRU_HASH, BPF_MAP_TYPE_PERCPU_HASH, BPF_MAP_TYPE_LRU_PERCPU_HASHBPF_MAP_TYPE_RINGBUF
Model konkurensiPencarian peta kernel bersifat atomik; beberapa pembaca dapat melakukan polling tanpa penguncian ruang penggunaSemantik baca destruktif memerlukan satu thread pembaca yang dikirim ke konsumen
Contoh kasus penggunaanHistogram latensi manajemen memori, ukuran alokasi Direct Memory Access Buffer (DMA-BUF), iterator sumber bangunPeristiwa pelacakan penjadwal yang difilter, pelacakan daya subsistem frekuensi tinggi

Metrik telemetri kernel bawaan

Android menyertakan program telemetri kernel eBPF bawaan yang berfungsi di seluruh kernel GKI 6.1 dan yang lebih tinggi, termasuk android14-6.1, android15-6.6, android16-6.12, dan android17-6.18. Setiap metrik latensi dan ukuran menggunakan histogram eksponensial (log2) 32 bucket yang digabungkan selama periode 24 jam. Tabel berikut mencantumkan metrik telemetri kernel bawaan:

Atom metrikSubsistemDeskripsiKunci dimensi
KswapdLatencyManajemen memoriKernel swap daemon, kswapd, latensi eksekusi bangun-ke-tidur dalam mikrodetikNon-Uniform Memory Access (NUMA) node_id
DirectReclaimLatencyManajemen memoriLatensi klaim memori langsung sinkron dalam mikrodetikStatus proses kehabisan memori (OOM) oom_score_adj
CompactionDurationManajemen memoriDurasi eksekusi pemadatan memori di seluruh sistem dalam mikrodetik, menggantikan hook vendor pemadatan lamaDi seluruh sistem
CmaAllocationLatencyManajemen memoriLatensi alokasi memori halaman Contiguous Memory Allocator (CMA) dalam mikrodetikNama heap CMA
DmabufRequestSizesManajemen memoriUkuran permintaan alokasi DMA-BUF dalam byteNama heap pengekspor
SuspendResumeLatencyPengelolaan dayaLatensi di seluruh tahap penangguhan dan melanjutkan kernel dalam mikrodetikTahap tindakan penangguhan

Menambahkan metrik telemetri kernel

Untuk menambahkan metrik akumulator baru ke pipeline telemetri kernel eBPF, selesaikan langkah-langkah berikut secara berurutan:

  1. Tulis program eBPF di bawah system/bpfprogs/telemetry/ dan buat biner ke /system/etc/bpf/kernelstats/.
  2. Verifikasi kompatibilitas tata letak struct BTF untuk pembaca BTF JNI generik.
  3. Daftarkan atom pull di statsd.
  4. Tentukan skema protobuf atom pull.

Bagian berikut menggunakan DirectReclaimLatency (directReclaim.c) sebagai contoh.

Menulis program eBPF

Di system/bpfprogs/telemetry/, sertakan <bpf_telemetry.h> dan tentukan kunci peta serta peta akumulator output. Gunakan ID bilangan bulat atau enum di kunci peta BPF, bukan string mentah untuk meminimalkan jejak memori peta dan menjaga privasi:

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

Di pengendali tracepoint, catat stempel waktu mulai saat entri, hitung bucket latensi log2 saat keluar menggunakan GET_LATENCY_BUCKET, dan tambahkan entri peta akumulator dengan 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;
}

Memverifikasi kompatibilitas tata letak struct BTF

Anda tidak perlu menulis kode C++ JNI kustom saat menambahkan peta histogram baru selama struct kunci dan nilai peta Anda menggunakan jenis array tetap, enum, dan bilangan bulat standar. Daripada mentransmisikan entri peta BPF ke struct C hardcode saat waktu kompilasi, pembaca JNI generik menyelesaikan tata letak struct secara dinamis saat runtime dalam tiga fase:

  • Asosiasi ELF ke peta: Lapisan JNI mengurai metadata BTF langsung dari file BPF ELF yang dikompilasi di disk, seperti /system/etc/bpf/kernelstats/directReclaim.bpf, menggunakan fungsi btf__parse libbpf.
  • Resolusi jenis BTF: Saat akses pertama, pembaca membuat dan menyimpan dalam cache representasi struktural kunci peta dan struct C nilai, menghitung offset byte kumulatif untuk anggota struct, lebar byte bilangan bulat dan enum, serta ukuran elemen array dari pointer dasar.
  • Pencarian dan serialisasi batch: Saat statsd menarik peta, lapisan JNI mengeluarkan satu perintah BPF_MAP_LOOKUP_BATCH ke syscall sys_bpf untuk membaca semua entri hash secara massal dan menyalin ID kunci yang diselesaikan dan array bucket langsung ke dalam array long[] Java primitif menggunakan fungsi JNI SetLongArrayRegion.

Jika struct C BPF berubah dalam rilis mendatang, bagian BTF yang dikompilasi ulang akan otomatis diperbarui dan pembaca JNI akan beradaptasi saat runtime tanpa memerlukan kompilasi ulang JNI.

Mendaftarkan atom pull di statsd

Di StatsPullAtomService.java, helper pullKernelTelemetry generik membaca peta BPF yang disematkan, menghitung delta bucket terhadap jumlah yang di-cache dari pull sebelumnya, dan memancarkan entri StatsEvent. Menambahkan metrik telemetri kernel baru hanya memerlukan tiga penambahan kecil:

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

Menentukan protobuf atom pull

Tentukan pesan atom pull yang diketik dengan kuat di frameworks/proto_logging/ agar sesuai dengan kunci dimensi dan array histogram log2 32 bucket yang dihasilkan oleh peta 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;
}