جمع‌آوری دورسنجی هسته با eBPF

این صفحه نمای کلی‌ای از چارچوب دورسنجی هسته «فیلتر بسته برکلی توسعه‌یافته» (eBPF) در Android ارائه می‌دهد و معماری چارچوب، سنجه‌های داخلی، و نحوه افزودن سنجه‌های هسته جدید را شرح می‌دهد.

‫Android شامل چارچوب تله‌متری هسته eBPF یکپارچه برای جمع‌آوری سنجه‌های هسته با سربار کم و نمایان کردن آن سنجه‌ها برای سرویس‌های فضای کاربر مثل statsd است. با پیوست کردن برنامه‌های قابل‌حمل eBPF به نقاط ردیابی هسته و kprobes، چارچوب رویدادهای هسته با بسامد بالا را درجا تجمیع می‌کند و هیستوگرام‌ها یا شمارنده‌های فشرده را بدون تغییر دادن تصویر هسته عمومی (GKI) یا شکستن «میانای واحد هسته» (KMI) صادر می‌کند.

رویکردهای سنتی به تله‌متری هسته به procfs یا sysfs گره‌های سفارشی، وصله‌های هسته خارج از درخت، یا قلاب‌های فروشنده که نیازمند لوله‌کشی سفارشی در سراسر هسته، SELinux، میانای بومی جاوا (JNI)، و سرویس‌های فضای کاربر هستند، متکی است. چارچوب دورسنجی هسته eBPF این خط لوله را در سراسر هسته‌های مشترک Android (ACK) استاندارد می‌کند و مزایای زیر را ارائه می‌دهد:

  • از تلاطم KMI و ABI هسته جلوگیری می‌کند: برنامه‌های eBPF را به نقاط ردیابی هسته یا kprobes موجود متصل می‌کند بدون اینکه کد منبع هسته را اصلاح کند، میانای دودویی کاربردی (ABI) هسته را تغییر دهد، یا قلاب‌های فروشنده Android مانند TRACE_ANDROID_VH_* را اضافه کند. سازندگان دستگاه و شرکای سیستم روی تراشه (SoC) می‌توانند به‌جای افزودن قلاب‌های جدید فروشنده تله‌متری یا برای انتقال از قلاب‌های فروشنده موجود، از تله‌متری eBPF استفاده کنند.
  • قابلیت حمل بین نسخه‌های هسته را حفظ می‌کند: از BPF Compile Once - Run Everywhere (CO-RE) و BPF Type Format (BTF) استفاده می‌کند تا یک برنامه eBPF کامپایل‌شده واحد به‌طور خودکار با تغییرات چیدمان ساختار هسته داخلی در نسخه‌های GKI، مانند android14-6.1 و بالاتر، سازگار شود.
  • سربار سلامت سیستم را به‌حداقل می‌رساند: رویدادهای هسته با بسامد بالا را مستقیماً در منبع به هیستوگرام‌های فشرده ۳۲ سطلی لگاریتمی در مبنای ۲ (log2) فیلتر و تجمیع می‌کند، معمولاً کمتر از ۱ کیلوبایت در هر نقشه BPF. فضای کاربر ورودی‌های نقشه انبوهشی را یک‌بار در روز بااستفاده از یک BPF_MAP_LOOKUP_BATCH فراخوانی سیستمی دسته‌ای بازیابی می‌کند.
  • ادغام SELinux و JNI را ساده می‌کند: نقشه‌های تله‌متری را در /sys/fs/bpf/kernelstats/ سنجاق می‌کند تا وراثت نوع SELinux سطح دایرکتوری fs_bpf_kernelstats اعمال شود و جابه‌جایی‌های عضو ساختار را در زمان اجرا در JNI بااستفاده از فراداده BTF حل می‌کند تا کد تجزیه سفارشی C++ هنگام افزودن سنجه‌های جدید حذف شود.

برای کسب اطلاعات بیشتر درباره نحوه بار کردن و سنجاق کردن برنامه‌ها و نقشه‌های eBPF توسط Android، به گسترش هسته با eBPF مراجعه کنید.

نمای کلی معماری

خط لوله دورسنجی هسته eBPF مؤلفه‌های زیرساخت عمومی را از تعاریف خاص سنجه جدا می‌کند تا افزودن سنجه هسته جدید به حداقل تغییرات کد نیاز داشته باشد. جدول زیر اجزای خط لوله را شرح می‌دهد:

مؤلفهنقش در خط لولهحوزه
بارکننده BPF، bpfloaderبرنامه‌های eBPF کامپایل‌شده را از /system/etc/bpf/kernelstats/ در زمان راه‌اندازی بار می‌کند (اگر با هسته درحال اجرا سازگار باشد) و نقشه‌ها را به /sys/fs/bpf/kernelstats/ سنجاق می‌کند. وقتی غیرفعال یا پشتیبانی‌نشده باشد، از بار کردن تمیز رد می‌شود.Generic
خط‌مشی SELinux،‏ fs_bpf_kernelstatsبرچسب‌های SELinux سطح دایرکتوری را به /sys/fs/bpf/kernelstats/ اعمال می‌کند تا system_server بتواند نقشه‌های تله‌متری را بدون به‌روزرسانی‌های خط‌مشی SELinux به‌ازای هر سنجه بخواند.Generic
برنامه eBPF در system/bpfprogs/telemetry/به نقطه‌های ردیابی هسته یا kprobes پیوست می‌شود، رویدادها را فیلتر می‌کند، و معیارها را در نقشه‌های BPF تجمیع می‌کند.سفارشی براساس سنجه
خوانشگر JNI BTFفراداده BTF را از کد باینری «قالب اجرایی و پیوندپذیر BPF» (ELF) کامپایل‌شده در زمان اجرا تجزیه می‌کند، جابه‌جایی‌های عضو ساختار را در حافظه نهان ذخیره می‌کند، و خواندن دسته‌ای نقشه را در آرایه‌های اولیه جاوا انجام می‌دهد.Generic
کشنده فضای کاربر در statsdنقشه‌های هیستوگرام تجمعی را در یک برنامه روزانه می‌کشد، دلتاها را دربرابر سابقه ذخیره‌شده در حافظه پنهان محاسبه می‌کند، و اتم‌های کششی StatsEvent با نوع قوی می‌سازد.یاری‌رسان عمومی با یک ورودی case در هر اتم
طرح دورسنجیstatsd اتم کششی با نوع قوی protobuf و هیستوگرام سمت سرور یا تجمیع سنجه مقدار را تعریف می‌کند.سفارشی براساس سنجه

مدل‌های مصرف فضای کاربر

هنگام طراحی سنجه دورسنجی هسته، بسته به اینکه سنجه نشان‌دهنده وضعیت تجمعی است یا توالی رویداد مرتب‌شده، بین دو مدل مصرف فضای کاربر انتخاب کنید. جدول زیر این دو مدل را مقایسه می‌کند:

داراییانباشتگرهاجاری‌سازی‌ها
تعریفشمارنده‌ها یا هیستوگرام‌های تجمعی حالت‌دار و یکنواخت افزایشیتوالی مرتب‌شده رویدادهای هسته با مُهر زمان
الگوی دسترسیفضای کاربر درصورت درخواست، مثلاً هر ۲۴ ساعت یک‌بار ازطریق statsd، نظرسنجی می‌کندفضای کاربر به‌طور مداوم از میان‌گیر حلقه‌ای مصرف می‌کند تا از رویدادهای حذف‌شده جلوگیری کند
انواع نقشه BPF معمولیBPF_MAP_TYPE_HASH، BPF_MAP_TYPE_LRU_HASH، BPF_MAP_TYPE_PERCPU_HASH، BPF_MAP_TYPE_LRU_PERCPU_HASHBPF_MAP_TYPE_RINGBUF
مدل هم‌رسجستجوی نقشه هسته اتمی است؛ چندین خواننده می‌توانند بدون قفل کردن فضای کاربر نظرسنجی کنندمعناشناسی خواندن مخرب به یک رشته خواننده واحد نیاز دارد که به مصرف‌کنندگان ارسال شود
نمونه موارد استفادههیستوگرام‌های تأخیر مدیریت حافظه، اندازه‌های تخصیص «میان‌گیر دسترسی مستقیم به حافظه» (DMA-BUF)، تکرارکننده‌های منبع بیداریرویدادهای ردیابی زمان‌بندی فیلترشده، ردیابی‌های توان زیرسیستم با فرکانس بالا

سنجه‌های دورسنجی هسته داخلی

Android شامل برنامه‌های تله‌متری هسته eBPF داخلی است که در سراسر هسته‌های GKI نسخه ۶.۱ و بالاتر، ازجمله android14-6.1،‏ android15-6.6، android16-6.12، و android17-6.18 کار می‌کنند. هر سنجه تأخیر و اندازه از یک هیستوگرام نمایی ۳۲ سطلی (log2) استفاده می‌کند که در یک پنجره ۲۴ ساعته جمع‌آوری شده است. جدول زیر سنجه‌های تله‌متری هسته داخلی را فهرست می‌کند:

اتم سنجهزیرسیستمشرحکلید بُعد
KswapdLatencyمدیریت حافظهKernel swap daemon, kswapd, wake-to-sleep execution latency in microsecondsدسترسی غیریکنواخت به حافظه (NUMA) node_id
DirectReclaimLatencyمدیریت حافظهتأخیر بازیابی حافظه مستقیم همگام برحسب میکروثانیهپردازش وضعیت oom_score_adj کمبود حافظه (OOM)
CompactionDurationمدیریت حافظهمدت زمان اجرای فشرده‌سازی حافظه در سراسر سیستم برحسب میکروثانیه، جایگزین قلاب‌های فروشنده فشرده‌سازی قدیمیسراسری
CmaAllocationLatencyمدیریت حافظهتأخیر تخصیص صفحه «تخصیص‌دهنده حافظه پیوسته» (CMA) برحسب میکروثانیهنام پشته CMA
DmabufRequestSizesمدیریت حافظهاندازه‌های درخواست تخصیص DMA-BUF به بایتنام انباره صادرکننده
SuspendResumeLatencyمدیریت شارژ باتریتأخیر در مراحل تعلیق و ازسرگیری هسته به میکروثانیهمرحله کنش تعلیق

افزودن سنجه دورسنجی هسته

برای افزودن سنجه انباشتگر جدید به خط لوله تله‌متری هسته eBPF، مراحل زیر را به ترتیب تکمیل کنید:

  1. برنامه eBPF را در system/bpfprogs/telemetry/ بنویسید و باینری را در /system/etc/bpf/kernelstats/ بسازید.
  2. بررسی سازگاری طرح‌بندی ساختار BTF برای خواننده JNI BTF عمومی.
  3. اتم pull را در statsd ثبت کنید.
  4. طرحواره protobuf اتم کشش را تعریف کنید.

بخش‌های زیر از DirectReclaimLatency (directReclaim.c) به‌عنوان مثال استفاده می‌کنند.

نوشتن برنامه eBPF

در system/bpfprogs/telemetry/،‏ <bpf_telemetry.h> را اضافه کنید و کلید نقشه و نقشه انباشتگر خروجی را تعریف کنید. برای به‌حداقل رساندن ردپای حافظه نقشه و حفظ حریم خصوصی، از شناسه‌های عدد صحیح یا شمارشی در کلیدهای نقشه BPF به‌جای رشته‌های خام استفاده کنید:

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

در گرداننده‌های نقطه ردیابی، مُهر زمان شروع را در ورودی ثبت کنید، log2 سطل تأخیر را در خروجی بااستفاده از GET_LATENCY_BUCKET محاسبه کنید، و ورودی نقشه انباشتگر را با 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;
}

درستی‌سنجی سازگاری چیدمان ساختار BTF

هنگام افزودن نقشه هیستوگرام جدید، تا زمانی که ساختارهای مقدار و کلید نقشه شما از انواع عدد صحیح استاندارد، شمارشی، و آرایه ثابت استفاده کنند، نیازی به نوشتن کد C++ سفارشی JNI ندارید. به‌جای تبدیل ورودی‌های نقشه BPF به ساختارهای C کدگذاری‌شده در زمان ترجمه، خواننده JNI عمومی طرح‌بندی‌های ساختار را به‌طور پویا در زمان اجرا در سه مرحله حل می‌کند:

  • ارتباط ELF-to-map: لایه JNI فراداده BTF را مستقیماً از فایل BPF ELF کامپایل‌شده در دیسک، مانند /system/etc/bpf/kernelstats/directReclaim.bpf، بااستفاده از تابع libbpf btf__parse تجزیه می‌کند.
  • وضوح نوع BTF: در اولین دسترسی، خواننده یک نمایش ساختاری از ساختارهای C کلید و مقدار نقشه را می‌سازد و ذخیره می‌کند، و افست‌های بایت تجمعی را برای اعضای ساختار، عرض‌های بایت عدد صحیح و enum، و اندازه‌های عنصر آرایه از اشاره‌گر پایه محاسبه می‌کند.
  • جستجوی دسته‌ای و سریال‌سازی: وقتی statsd نقشه را می‌کشد، لایه JNI یک فرمان BPF_MAP_LOOKUP_BATCH به سیستم‌فراخوان sys_bpf صادر می‌کند تا همه ورودی‌های درهم‌سازی را به‌صورت انبوه بخواند و شناسه‌های کلید حل‌وفصل‌شده و آرایه‌های سطلی را مستقیماً در آرایه‌های long[] اولیه جاوا بااستفاده از تابع SetLongArrayRegion JNI کپی می‌کند.

اگر ساختار BPF C در نسخه پخش بعدی تغییر کند، بخش BTF بازکامپایل‌شده به‌طور خودکار به‌روزرسانی می‌شود و خواننده JNI در زمان اجرا بدون نیاز به بازکامپایل JNI تطبیق پیدا می‌کند.

ثبت اتم کشش در statsd

در StatsPullAtomService.java، دستیار عمومی pullKernelTelemetry نقشه BPF سنجاق‌شده را می‌خواند، تغییرات دلتای دسته‌ها را دربرابر تعداد ذخیره‌شده در حافظه نهان کشش قبلی محاسبه می‌کند، و StatsEvent ورودی منتشر می‌کند. افزودن سنجه دورسنجی هسته جدید فقط به سه مورد اضافه کوچک نیاز دارد:

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

تعریف کردن پروتوباف اتم کششی

پیام اتم کششی با نوع قوی را در frameworks/proto_logging/ تعریف کنید تا با کلید بُعد و آرایه هیستوگرام log2 ۳۲-سطل تولیدشده توسط نقشه 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;
}