本頁面概略說明 Android 的 Extended Berkeley Packet Filter (eBPF) 核心遙測架構,包括架構、內建指標,以及如何新增核心指標。
Android 包含統一的 eBPF 核心遙測架構,用於收集低負載核心指標,並將這些指標公開給使用者空間服務,例如 statsd。只要將可攜式 eBPF 程式附加至核心追蹤點和 kprobe,架構就能在適當位置匯總高頻率核心事件,並匯出精簡的直方圖或計數器,不必修改通用核心映像檔 (GKI) 或中斷核心模組介面 (KMI)。
傳統的核心遙測方法仰賴自訂 procfs 或 sysfs 節點、樹狀結構外核心修補程式,或需要跨核心、SELinux、Java 原生介面 (JNI) 和使用者空間服務自訂管道的供應商掛鉤。eBPF 核心遙測架構可將這個管道標準化,適用於 Android Common Kernels (ACK),並提供下列優點:
- 避免核心 ABI 和 KMI 變動:將 eBPF 程式附加至現有核心追蹤點或 kprobe,不必修改核心原始碼、變更核心應用程式二進位檔介面 (ABI),或新增 Android 供應商掛鉤 (例如
TRACE_ANDROID_VH_*)。裝置製造商和系統單晶片 (SoC) 合作夥伴可以使用 eBPF 遙測技術,取代新增遙測供應商掛鉤,或從現有供應商掛鉤遷移。 - 維持跨核心版本的可攜性:使用 BPF Compile Once - Run Everywhere (CO-RE) 和 BPF Type Format (BTF),讓單一編譯的 eBPF 程式自動適應跨 GKI 版本 (例如
android14-6.1以上版本) 的內部核心結構版面配置變更。 - 減少系統健康狀態的負擔:直接在來源將高頻率的 Kernel 事件篩選並匯總為精簡的 32 個 bucket base-2 對數 (
log2) 直方圖,通常每個 BPF 對應的資料大小不到 1 KB。使用者空間每天會使用單一BPF_MAP_LOOKUP_BATCH批次系統呼叫,擷取匯總的地圖項目一次。 - 簡化 SELinux 和 JNI 整合:將遙測對應固定在
/sys/fs/bpf/kernelstats/下,套用目錄層級fs_bpf_kernelstatsSELinux 類型繼承,並在執行階段使用 BTF 中繼資料解析 JNI 中的 struct 成員偏移,在新增指標時不需自訂 C++ 剖析程式碼。
如要進一步瞭解 Android 如何載入及固定 eBPF 程式和對應,請參閱「使用 eBPF 擴充核心」。
架構總覽
eBPF 核心遙測管道會將一般基礎架構元件與指標專屬定義分開,因此新增核心指標時,只需稍微變更程式碼。下表說明管道的元件:
| 元件 | 管道中的角色 | 範圍 |
|---|---|---|
BPF 載入器,bpfloader | 如果與執行中的核心相容,則在啟動時從 /system/etc/bpf/kernelstats/ 載入已編譯的 eBPF 程式,並將對應固定至 /sys/fs/bpf/kernelstats/。停用或不支援時,會略過載入。 | 一般 |
SELinux 政策,fs_bpf_kernelstats | 將目錄層級的 SELinux 標籤套用至 /sys/fs/bpf/kernelstats/,讓 system_server 讀取遙測對應,不必更新個別指標的 SELinux 政策。 | 一般 |
system/bpfprogs/telemetry/中的 eBPF 程式 | 附加至核心追蹤點或 kprobe、篩選事件,並將指標匯總至 BPF 地圖。 | 依指標自訂 |
| JNI BTF 讀取器 | 在執行階段剖析編譯 BPF 可執行和可連結格式 (ELF) 二進位檔中的 BTF 中繼資料、快取結構體成員偏移,並執行批次對應讀取至原始 Java 陣列。 | 一般 |
statsd 中的使用者空間提取器 | 每天提取累積直方圖對應、計算與快取記錄的差異,並建構強型別 StatsEvent 提取原子。 | 每個原子都有單一 case 項目的通用輔助程式 |
| 遙測結構定義 | 定義強型別 statsd 提取原子 protobuf 和伺服器端直方圖或值指標匯總。 | 依指標自訂 |
使用者空間計費模式
設計核心遙測指標時,請根據指標代表累計狀態或排序事件序列,選擇兩種使用者空間消耗模式。下表比較這兩種模型:
| 屬性 | 累加器 | 串流 |
|---|---|---|
| 定義 | 有狀態、單調遞增的累計計數器或直方圖 | 依時間順序排列的含時間戳記核心事件 |
| 存取模式 | 視需要進行使用者空間輪詢,例如透過 statsd 每 24 小時輪詢一次 | 使用者空間會持續從環形緩衝區取用資料,避免事件遭到捨棄 |
| 常見的 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。每個延遲時間和大小指標都會使用 32 個儲存區的指數 (log2) 直方圖,並在 24 小時內匯總。下表列出內建的核心遙測指標:
| 指標原子 | 子系統 | 說明 | 維度鍵 |
|---|---|---|---|
KswapdLatency | 記憶體管理 | 核心交換 Daemon kswapd 從喚醒到休眠的執行延遲時間 (以微秒為單位) | 非統一記憶體存取 (NUMA) node_id |
DirectReclaimLatency | 記憶體管理 | 同步直接記憶體回收延遲時間 (微秒) | 程序記憶體不足 (OOM) oom_score_adj 狀態 |
CompactionDuration | 記憶體管理 | 全系統記憶體壓縮執行時間長度 (以微秒為單位),取代舊版壓縮供應商掛鉤 | 系統通用 |
CmaAllocationLatency | 記憶體管理 | 連續記憶體配置器 (CMA) 頁面配置延遲時間 (以微秒為單位) | CMA 堆積名稱 |
DmabufRequestSizes | 記憶體管理 | DMA-BUF 分配要求大小 (以位元組為單位) | 匯出工具堆積名稱 |
SuspendResumeLatency | 電源管理 | 核心暫停和恢復階段的延遲時間 (以微秒為單位) | 暫停動作階段 |
新增核心遙測指標
如要在 eBPF 核心遙測管道中新增累加器指標,請依序完成下列步驟:
- 在
system/bpfprogs/telemetry/下編寫 eBPF 程式,並將二進位檔建構至/system/etc/bpf/kernelstats/。 - 驗證 BTF 結構體版面配置相容性,適用於一般 JNI BTF 讀取器。
- 在
statsd中註冊提取原子。 - 定義提取原子 protobuf 結構定義。
以下各節會以 DirectReclaimLatency (directReclaim.c) 為例。
編寫 eBPF 程式
在 system/bpfprogs/telemetry/ 中,加入 <bpf_telemetry.h> 並定義地圖索引鍵和輸出累加器地圖。在 BPF 對應鍵中使用整數 ID 或列舉,而非原始字串,以盡量減少對應記憶體用量並維護隱私權:
#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);
在追蹤點處理常式中,記錄輸入時的開始時間戳記,使用 GET_LATENCY_BUCKET 計算結束時的延遲時間log2 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++ 程式碼。一般 JNI 讀取器不會在編譯時將 BPF 對應項目轉換為硬式編碼的 C 結構體,而是在執行階段動態解析結構體版面配置,分為三個階段:
- ELF 對應關聯:JNI 層會使用
libbpfbtf__parse函式,直接從磁碟上編譯的 BPF ELF 檔案 (例如/system/etc/bpf/kernelstats/directReclaim.bpf) 剖析 BTF 中繼資料。 - BTF 類型解析:首次存取時,讀取器會建構並快取地圖鍵和值 C struct 的結構表示法,從基本指標計算 struct 成員的累計位元組偏移、整數和列舉位元組寬度,以及陣列元素大小。
- 批次查詢和序列化:
statsd提取對應時,JNI 層會向sys_bpf系統呼叫發出單一BPF_MAP_LOOKUP_BATCH指令,大量讀取所有雜湊項目,並使用SetLongArrayRegionJNI 函式,將解析後的金鑰 ID 和值區陣列直接複製到原始 Javalong[]陣列。
如果 BPF C 結構在未來版本中有所變更,重新編譯的 BTF 區段會自動更新,JNI 讀取器也會在執行階段調整,不需要重新編譯 JNI。
在 statsd 中註冊提取原子
在 StatsPullAtomService.java 中,一般 pullKernelTelemetry 輔助程式會讀取已釘選的 BPF 對應,根據先前提取的快取計數計算 bucket 差異,並發出 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";
定義提取原子 protobuf
在 frameworks/proto_logging/ 中定義強型別的提取原子訊息,以符合 BPF 對應產生的維度鍵和 32 個 bucket 的直方圖陣列:log2
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;
}