이 페이지에서는 Android의 확장 버클리 패킷 필터 (eBPF) 커널 원격 분석 프레임워크에 대한 개요를 제공하고 프레임워크 아키텍처, 기본 제공 측정항목, 새 커널 측정항목을 추가하는 방법을 설명합니다.
Android에는 낮은 오버헤드 커널 측정항목을 수집하고 이러한 측정항목을 statsd와 같은 사용자 공간 서비스에 노출하기 위한 통합 eBPF 커널 원격 분석 프레임워크가 포함되어 있습니다. 휴대용 eBPF 프로그램을 커널 트레이스포인트 및
kprobes에 연결하면 프레임워크는 고주파 커널 이벤트를 제자리에서 집계하고
일반 커널 이미지 (GKI)를 수정하거나 커널 모듈 인터페이스 (KMI)를 중단하지 않고
압축된 히스토그램 또는 카운터를 내보냅니다.
커널 원격 분석에 대한 기존 접근 방식은 커널, SELinux, Java 네이티브 인터페이스 (JNI), 사용자 공간 서비스 전반에 걸쳐 맞춤 배관이 필요한 맞춤 procfs 또는 sysfs 노드, 트리 외부 커널 패치 또는 공급업체 후크에 의존합니다.
eBPF 커널 원격 분석 프레임워크는 Android 공통 커널 (ACK) 전반에서 이 파이프라인을 표준화하고 다음과 같은 이점을 제공합니다.
- 커널 ABI 및 KMI 변동 방지: 커널 소스 코드를 수정하거나 커널 애플리케이션 바이너리 인터페이스 (ABI)를 변경하거나
TRACE_ANDROID_VH_*와 같은 Android 공급업체 후크를 추가하지 않고 eBPF 프로그램을 기존 커널 트레이스포인트 또는 kprobes에 연결합니다. 기기 제조업체 및 시스템 온 칩(SoC) 파트너는 새 원격 분석 공급업체 후크를 추가하는 대신 또는 기존 공급업체 후크에서 이전하기 위해 eBPF 원격 분석을 사용할 수 있습니다. - 커널 버전 간의 이식성 유지: BPF 컴파일 한 번 - 어디서나 실행 (CO-RE) 및 BPF 유형 형식 (BTF)을 사용하므로 단일 컴파일된 eBPF 프로그램이
android14-6.1이상과 같은 GKI 버전 간의 내부 커널 구조 레이아웃 변경에 자동으로 적응합니다. - 시스템 상태 오버헤드 최소화: 일반적으로 BPF 맵당 1KB 미만의 압축된 32버킷 밑이 2인 로그 (
log2) 히스토그램으로 소스에서 직접 고주파 커널 이벤트를 필터링하고 집계합니다. 사용자 공간은 단일BPF_MAP_LOOKUP_BATCH일괄 syscall을 사용하여 하루에 한 번 집계된 맵 항목을 검색합니다. - SELinux 및 JNI 통합 간소화: 디렉터리 수준
fs_bpf_kernelstatsSELinux 유형 상속을 적용하기 위해/sys/fs/bpf/kernelstats/아래에 원격 분석 맵을 고정하고 새 측정항목을 추가할 때 맞춤 C++ 파싱 코드를 삭제하기 위해 BTF 메타데이터를 사용하여 JNI에서 런타임에 구조체 멤버 오프셋을 확인합니다.
Android에서 eBPF 프로그램과 맵을 로드하고 고정하는 방법에 관한 자세한 내용은 eBPF로 커널 확장을 참고하세요.
아키텍처 개요
eBPF 커널 원격 분석 파이프라인은 일반 인프라 구성요소를 측정항목별 정의와 분리하므로 새 커널 측정항목을 추가하는 데 최소한의 코드 변경이 필요합니다. 다음 표에서는 파이프라인의 구성요소를 설명합니다.
| 구성요소 | 파이프라인의 역할 | 범위 |
|---|---|---|
BPF 로더, bpfloader | 실행 중인 커널과 호환되는 경우 부팅 시 /system/etc/bpf/kernelstats/에서 컴파일된 eBPF 프로그램을 로드하고 맵을 /sys/fs/bpf/kernelstats/에 고정합니다. 사용 중지되거나 지원되지 않는 경우 로드를 깔끔하게 건너뜁니다. | Generic |
SELinux 정책, fs_bpf_kernelstats | system_server가 측정항목별 SELinux 정책 업데이트 없이 원격 분석 맵을 읽을 수 있도록 디렉터리 수준 SELinux 라벨을 /sys/fs/bpf/kernelstats/에 적용합니다. | Generic |
system/bpfprogs/telemetry/의 eBPF 프로그램 | 커널 트레이스포인트 또는 kprobes에 연결하고, 이벤트를 필터링하고, 측정항목을 BPF 맵으로 집계합니다. | 측정항목별 맞춤 |
| JNI BTF 리더 | 런타임에 컴파일된 BPF 실행 파일 및 연결 가능한 형식 (ELF) 바이너리에서 BTF 메타데이터를 파싱하고, 구조체 멤버 오프셋을 캐시하고, 기본 Java 배열로 일괄 맵 읽기를 실행합니다. | Generic |
statsd의 사용자 공간 풀러 | 일일 일정에 따라 누적 히스토그램 맵을 가져오고, 캐시된 기록에 대해 델타를 계산하고, 강력한 유형의 StatsEvent 풀 atom을 빌드합니다. | atom당 단일 case 항목이 있는 일반 도우미 |
| 원격 분석 스키마 | 강력한 유형의 statsd 풀 atom 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에는 android14-6.1, android15-6.6, android16-6.12, android17-6.18을 비롯한 GKI 6.1 이상 커널에서 작동하는 기본 제공 eBPF 커널 원격 분석 프로그램이 포함되어 있습니다. 각 지연 시간 및 크기 측정항목은 24시간 창에서 집계된 32버킷 지수 (log2) 히스토그램을 사용합니다. 다음 표에는 기본 제공 커널 원격 분석 측정항목이 나와 있습니다.
| 측정항목 atom | 하위 시스템 | 설명 | 측정기준 키 |
|---|---|---|---|
KswapdLatency | 메모리 관리 | 커널 스왑 데몬, kswapd, 절전 모드 해제 실행 지연 시간(마이크로초) | 비균일 메모리 액세스 (NUMA) node_id |
DirectReclaimLatency | 메모리 관리 | 동기식 직접 메모리 회수 지연 시간(마이크로초) | 프로세스 메모리 부족 (OOM) oom_score_adj 상태 |
CompactionDuration | 메모리 관리 | 시스템 전체 메모리 압축 실행 시간(마이크로초), 기존 압축 공급업체 후크 대체 | 시스템 전체 |
CmaAllocationLatency | 메모리 관리 | 연속 메모리 할당자(CMA) 페이지 할당 지연 시간(마이크로초) | CMA 힙 이름 |
DmabufRequestSizes | 메모리 관리 | DMA-BUF 할당 요청 크기(바이트) | 내보내기 힙 이름 |
SuspendResumeLatency | 전원 관리 | 커널 일시중지 및 재개 단계의 지연 시간(마이크로초) | 일시중지 작업 단계 |
커널 원격 분석 측정항목 추가
eBPF 커널 원격 분석 파이프라인에 새 누산기 측정항목을 추가하려면 다음 단계를 순서대로 완료하세요.
- eBPF 프로그램을
system/bpfprogs/telemetry/아래에 작성하고 바이너리를/system/etc/bpf/kernelstats/로 빌드합니다. - 일반 JNI BTF 리더의 BTF 구조 레이아웃 호환성을 확인합니다.
statsd에 풀 atom을 등록합니다.- 풀 atom protobuf 스키마를 정의합니다.
다음 섹션에서는 DirectReclaimLatency (directReclaim.c)를 예로 사용합니다.
eBPF 프로그램 작성
system/bpfprogs/telemetry/에서 <bpf_telemetry.h>를 포함하고 맵
키와 출력 누산기 맵을 정의합니다. 맵 메모리 사용량을 최소화하고 개인 정보를 보호하려면 원시 문자열 대신 BPF 맵 키에 정수 식별자 또는 enum을 사용하세요.
#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 지연 시간 버킷을 계산하고, 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 구조 레이아웃 호환성 확인
맵 키와 값 구조체가 표준 정수, enum, 고정 배열 유형을 사용하는 한 새 히스토그램 맵을 추가할 때 맞춤 JNI C++ 코드를 작성할 필요가 없습니다. 컴파일 시간에 BPF 맵 항목을 하드 코딩된 C 구조체로 캐스팅하는 대신 일반 JNI 리더는 런타임에 세 단계로 구조 레이아웃을 동적으로 확인합니다.
- ELF-to-map 연결: JNI 레이어는
libbpfbtf__parse함수를 사용하여 디스크의 컴파일된 BPF ELF 파일(예:/system/etc/bpf/kernelstats/directReclaim.bpf)에서 직접 BTF 메타데이터를 파싱합니다. - BTF 유형 확인: 첫 번째 액세스 시 리더는 맵의 키 및 값 C 구조체의 구조적 표현을 빌드하고 캐시하여 구조체 멤버, 정수 및 enum 바이트 너비, 배열 요소 크기의 누적 바이트 오프셋을 기본 포인터에서 계산합니다.
- 일괄 조회 및 직렬화:
statsd가 맵을 가져오면 JNI 레이어는sys_bpfsyscall에 단일BPF_MAP_LOOKUP_BATCH명령어를 실행하여 모든 해시 항목을 일괄적으로 읽고 확인된 키 ID와 버킷 배열을SetLongArrayRegionJNI 함수를 사용하여 기본 Javalong[]배열에 직접 복사합니다.
향후 출시에서 BPF C 구조체가 변경되면 재컴파일된 BTF 섹션이 자동으로 업데이트되고 JNI 리더가 JNI 재컴파일 없이 런타임에 적응합니다.
statsd에 풀 atom 등록
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";
풀 atom protobuf 정의
BPF 맵에서 생성된 측정기준 키 및 32버킷 log2 히스토그램 배열과 일치하도록 frameworks/proto_logging/에서 강력한 유형의 풀 atom 메시지를 정의합니다.
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;
}