تقدّم هذه الصفحة نظرة عامة على إطار عمل القياس عن بُعد لنواة Extended Berkeley Packet Filter (eBPF) في Android، وتصف بنية إطار العمل والمقاييس المضمّنة وكيفية إضافة مقاييس جديدة للنواة.
يتضمّن Android إطار عمل موحّدًا للقياس عن بُعد لنواة eBPF من أجل جمع مقاييس النواة ذات النفقات المنخفضة وعرض هذه المقاييس على خدمات مساحة المستخدم، مثل statsd. من خلال ربط برامج eBPF المحمولة بنقاط تتبُّع النواة و
kprobes، يجمّع إطار العمل أحداث النواة عالية التردد في مكانها و
يصدّر عدّادات أو مدرّجات تكرارية مضغوطة بدون تعديل
صورة النواة العامة (GKI) أو إيقاف واجهة وحدة النواة (KMI).
تعتمد الطرق التقليدية للقياس عن بُعد لنواة على عُقد procfs أو sysfs المخصّصة، أو تصحيحات النواة خارج الشجرة، أو روابط برامج التشغيل التي تتطلب ربطًا مخصّصًا عبر النواة وSELinux وJava Native Interface (JNI) وخدمات مساحة المستخدم.
يضع إطار عمل القياس عن بُعد لنواة eBPF معيارًا لهذا المسار في جميع النواة الشائعة في Android (ACKs) ويقدّم المزايا التالية:
- تجنُّب تغيير واجهة التطبيق الثنائية (ABI) للنواة وواجهة وحدة النواة (KMI): يربط برامج 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والإصدارات الأحدث. - تقليل النفقات العامة لصحة النظام إلى الحد الأدنى: يفلتر ويجمّع أحداث النواة عالية التردد مباشرةً في المصدر في مدرّجات تكرارية مضغوطة ذات 32 خانة أساسها لوغاريتمي ثنائي (
log2)، عادةً أقل من 1 كيلوبايت لكل خريطة BPF. تسترد مساحة المستخدم إدخالات الخريطة المجمّعة مرة واحدة في اليوم باستخدام استدعاء نظام مجمّع واحدBPF_MAP_LOOKUP_BATCH. - تبسيط عملية الدمج مع SELinux وJNI: يثبّت خرائط القياس عن بُعد ضمن
/sys/fs/bpf/kernelstats/لتطبيق وراثة نوع SELinux على مستوى الدليلfs_bpf_kernelstats، ويحلّ إزاحات أعضاء البنية في وقت التشغيل في JNI باستخدام بيانات BTF الوصفية لإزالة رمز التحليل المخصّص بلغة C++ عند إضافة مقاييس جديدة.
لمزيد من المعلومات حول كيفية تحميل Android لبرامج eBPF وخرائطها وتثبيتها، يُرجى الاطّلاع على توسيع النواة باستخدام 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) المجمّع في وقت التشغيل، ويخزّن إزاحات أعضاء البنية مؤقتًا، ويُجري عمليات قراءة مجمّعة للخريطة في مصفوفات Java الأساسية. | Generic |
أداة سحب مساحة المستخدم في statsd | تسحب خرائط المدرّج التكراري التراكمي وفقًا لجدول زمني يومي، وتحسب الفروق مقابل السجلّ المخزّن مؤقتًا، وتنشئ ذرات سحب StatsEvent ذات أنواع محدّدة. | أداة مساعدة عامة تتضمّن إدخال case واحدًا لكل ذرة |
| مخطط القياس عن بُعد | يحدّد بروتوكول statsd لذرة السحب ذات النوع المحدّد وعمليات التجميع من جهة الخادم للمدرّج التكراري أو مقياس القيمة. | مخصّص لكل مقياس |
نماذج استخدام مساحة المستخدم
عند تصميم مقياس للقياس عن بُعد لنواة، اختَر بين نموذجَين لاستخدام مساحة المستخدم، وذلك حسب ما إذا كان المقياس يمثّل حالة تراكمية أو تسلسلاً منظّمًا للأحداث. يقارن الجدول التالي بين النموذجَين:
| السمة | العدادات التراكمية | عدد مرّات المشاهدة |
|---|---|---|
| التعريف | عدادات أو مدرّجات تكرارية تراكمية متزايدة بشكل منتظم ومستمرة الحالة | تسلسل منظّم لأحداث النواة التي تحمل طابعًا زمنيًا |
| نمط الوصول | تستطلع مساحة المستخدم البيانات عند الطلب، مثلاً مرة واحدة كل 24 ساعة من خلال statsd | تستهلك مساحة المستخدم البيانات باستمرار من مخزن مؤقت حلقي لتجنُّب فقدان الأحداث |
| أنواع خرائط BPF النموذجية | BPF_MAP_TYPE_HASH وBPF_MAP_TYPE_LRU_HASH وBPF_MAP_TYPE_PERCPU_HASH وBPF_MAP_TYPE_LRU_PERCPU_HASH | BPF_MAP_TYPE_RINGBUF |
| نموذج التزامن | عمليات البحث في خريطة النواة ذرية، ويمكن لعدة قراء الاستطلاع بدون قفل مساحة المستخدم | تتطلب دلالات القراءة المدمّرة سلسلة محادثات قارئ واحدة تُرسِل البيانات إلى المستهلكين |
| أمثلة على حالات الاستخدام | مدرّجات تكرارية لوقت استجابة إدارة الذاكرة، وأحجام تخصيص مخزن ذاكرة الوصول المباشر (DMA-BUF)، وعناصر تكرار مصدر التنبيه | أحداث تتبُّع أداة الجدولة المفلترة، وعمليات تتبُّع الطاقة عالية التردد للنظام الفرعي |
مقاييس القياس عن بُعد المضمّنة للنواة
يتضمّن Android برامج قياس عن بُعد مضمّنة لنواة eBPF تعمل على النواة GKI 6.1 والإصدارات الأحدث، بما في ذلك android14-6.1 وandroid15-6.6 وandroid16-6.12 وandroid17-6.18. يستخدم كل مقياس لوقت الاستجابة والحجم مدرّجًا تكراريًا أسيًا (log2) ذا 32 خانة يتم تجميعه على مدار نافذة مدتها 24 ساعة. يسرد الجدول التالي مقاييس القياس عن بُعد المضمّنة للنواة:
| ذرة المقياس | النظام الفرعي | الوصف | مفتاح السمة |
|---|---|---|---|
KswapdLatency | إدارة الذاكرة | وقت استجابة تنفيذ برنامج تبديل النواة، kswapd، من التنبيه إلى النوم بالميكروثانية | node_id للوصول إلى الذاكرة غير الموحّد (NUMA) |
DirectReclaimLatency | إدارة الذاكرة | وقت استجابة استعادة الذاكرة المباشرة المتزامنة بالميكروثانية | حالة oom_score_adj للعملية التي نفدت منها الذاكرة (OOM) |
CompactionDuration | إدارة الذاكرة | مدة تنفيذ ضغط الذاكرة على مستوى النظام بالميكروثانية، ما يحل محل روابط برامج التشغيل القديمة لضغط الذاكرة | على مستوى النظام |
CmaAllocationLatency | إدارة الذاكرة | وقت استجابة تخصيص صفحة "مخصّص الذاكرة المتجاورة" (CMA) بالميكروثانية | اسم مجموعة CMA |
DmabufRequestSizes | إدارة الذاكرة | أحجام طلبات تخصيص DMA-BUF بالبايت | اسم مجموعة المُصدِّر |
SuspendResumeLatency | إدارة الطاقة | وقت الاستجابة خلال مراحل تعليق النواة واستئنافها بالميكروثانية | مرحلة إجراء التعليق |
إضافة مقياس للقياس عن بُعد لنواة
لإضافة مقياس عدّاد تراكمي جديد إلى مسار القياس عن بُعد لنواة eBPF، أكمل الخطوات التالية بالترتيب:
- اكتب برنامج eBPF ضمن
system/bpfprogs/telemetry/وأنشئ الملف الثنائي في/system/etc/bpf/kernelstats/. - تأكَّد من توافق تنسيق بنية BTF مع قارئ JNI BTF العام.
- سجِّل ذرة السحب في
statsd. - حدِّد مخطط بروتوكول ذرة السحب.
تستخدم الأقسام التالية 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
لست بحاجة إلى كتابة رمز JNI C++ مخصّص عند إضافة خريطة مدرّج تكراري جديد ما دامت بنية مفتاح الخريطة والقيمة تستخدم أنواعًا قياسية من الأعداد الصحيحة والتعدادات والمصفوفات الثابتة. بدلاً من تحويل إدخالات خريطة BPF إلى بنى C مبرمَجة مسبقًا في وقت التجميع، يحلّ قارئ JNI العام تنسيقات البنية ديناميكيًا في وقت التشغيل على ثلاث مراحل:
- الربط بين ELF والخريطة: تحلّل طبقة JNI بيانات BTF الوصفية مباشرةً من ملف BPF ELF المجمّع على القرص، مثل
/system/etc/bpf/kernelstats/directReclaim.bpf، باستخدام الدالةbtf__parseفيlibbpf. - حلّ نوع BTF: عند الوصول الأول، ينشئ القارئ تمثيلاً بنيويًا لبنيتَي C لمفتاح الخريطة والقيمة ويخزّنه مؤقتًا، ويحسب إزاحات البايت التراكمية لأعضاء البنية، وعرض البايت للأعداد الصحيحة والتعدادات، وأحجام عناصر المصفوفة من المؤشر الأساسي.
- عملية البحث والتسلسل المجمّعة: عندما يسحب
statsdالخريطة، تُصدر طبقة JNI أمرًا واحدًاBPF_MAP_LOOKUP_BATCHإلى استدعاء نظامsys_bpfلقراءة جميع إدخالات التجزئة بشكل مجمّع، وتنسخ معرّفات المفاتيح التي تم حلّها ومصفوفات الخانات مباشرةً إلى مصفوفاتlong[]في Java الأساسية باستخدام دالة JNISetLongArrayRegion.
إذا تغيّرت بنية 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 ذات 32 خانة التي تنتجها خريطة 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;
}