На этой странице представлен обзор фреймворка телеметрии ядра Extended Berkeley Packet Filter (eBPF) в Android, описана архитектура фреймворка, встроенные показатели и способы добавления новых показателей ядра.
В Android есть унифицированная платформа телеметрии ядра eBPF, которая собирает показатели ядра с низкими накладными расходами и предоставляет их сервисам пользовательского пространства, таким как statsd. Прикрепляя переносимые программы eBPF к точкам трассировки ядра и kprobes, фреймворк агрегирует высокочастотные события ядра на месте и экспортирует компактные гистограммы или счетчики, не изменяя GKI и не нарушая работу интерфейса модуля ядра (KMI).
Традиционные подходы к телеметрии ядра основаны на специальных узлах procfs или sysfs, исправлениях ядра вне дерева или хуках поставщиков, которые требуют специальной настройки ядра, SELinux, Java Native Interface (JNI) и сервисов пользовательского пространства.
Фреймворк телеметрии ядра eBPF стандартизирует этот конвейер для общих ядер Android (ACK) и дает следующие преимущества:
- Предотвращает отток ABI и KMI ядра. Программы eBPF подключаются к существующим точкам трассировки ядра или kprobes без изменения исходного кода ядра, изменения двоичного интерфейса приложения ядра (ABI) или добавления хуков поставщиков Android, таких как
TRACE_ANDROID_VH_*. Производители устройств и партнеры, выпускающие системы на кристалле, могут использовать телеметрию eBPF вместо добавления новых хуков поставщиков телеметрии или для перехода с существующих хуков. - Сохраняет переносимость между версиями ядра. Использует BPF Compile Once – Run Everywhere (CO-RE) и BPF Type Format (BTF), чтобы одна скомпилированная программа eBPF автоматически адаптировалась к изменениям внутренней структуры ядра в разных версиях GKI, например
android14-6.1и выше. - Минимизация нагрузки на систему. Фильтрует и агрегирует высокочастотные события ядра непосредственно в источнике в компактные логарифмические (
log2) гистограммы с основанием 2 и 32 интервалами, обычно менее 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 reader | Выполняет синтаксический анализ метаданных BTF из скомпилированного исполняемого и связываемого формата BPF (ELF) во время выполнения, кэширует смещения членов структуры и выполняет пакетное чтение карт в примитивные массивы Java. | Generic |
Утилита для извлечения данных из пользовательского пространства в statsd | Ежедневно извлекает накопительные гистограммы, вычисляет дельты по сравнению с сохраненными данными и создает атомы извлечения StatsEvent со строгой типизацией. | Универсальный помощник с одной записью case на атом |
| Схема телеметрии | Определяет строго типизированный атомарный protobuf-файл 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, задержка выполнения при переходе из режима ожидания в спящий режим в микросекундах | Неоднородный доступ к памяти (NUMA) node_id |
DirectReclaimLatency | Управление памятью | Задержка синхронного прямого освобождения памяти в микросекундах | Состояние процесса oom_score_adj при нехватке памяти |
CompactionDuration | Управление памятью | Длительность выполнения системной дефрагментации памяти в микросекундах, заменяющая устаревшие хуки поставщика дефрагментации | На уровне системы |
CmaAllocationLatency | Управление памятью | Задержка выделения страниц в непрерывном распределителе памяти (CMA) в микросекундах | Название кучи CMA |
DmabufRequestSizes | Управление памятью | Размеры запросов на выделение буфера DMA-BUF в байтах | Название кучи экспортера |
SuspendResumeLatency | Управление питанием | Задержка при переходе между этапами приостановки и возобновления работы ядра (в микросекундах) | Приостановить этап действия |
Добавить показатель телеметрии ядра
Чтобы добавить новый показатель аккумулятора в конвейер телеметрии ядра eBPF, выполните следующие действия по порядку:
- Напишите программу eBPF в
system/bpfprogs/telemetry/и соберите двоичный файл в/system/etc/bpf/kernelstats/. - Проверьте совместимость макета структуры BTF для универсального средства чтения BTF JNI.
- Зарегистрируйте pull-атом в
statsd. - Определите схему protobuf для атома pull.
В следующих разделах в качестве примера используется 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 непосредственно из скомпилированного файла ELF BPF на диске, например
/system/etc/bpf/kernelstats/directReclaim.bpf, с помощью функцииlibbpfbtf__parse. - Разрешение типа BTF. При первом доступе программа чтения создает и кеширует структурное представление ключа и значения C-структур карты, вычисляя кумулятивные смещения байтов для членов структуры, ширину байтов для целых чисел и перечислений, а также размеры элементов массива на основе базового указателя.
- Пакетный поиск и сериализация. Когда
statsdизвлекает карту, уровень JNI отправляет одну командуBPF_MAP_LOOKUP_BATCHсистемному вызовуsys_bpf, чтобы массово прочитать все записи хеша и скопировать разрешенные идентификаторы ключей и массивы сегментов непосредственно в примитивные массивыlong[]Java с помощью функции JNISetLongArrayRegion.
Если в будущем выпуске структура C BPF изменится, перекомпилированный раздел BTF обновится автоматически, а средство чтения JNI адаптируется во время выполнения без необходимости перекомпиляции JNI.
Как зарегистрировать атом pull в 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";
Определите протокол буфера атома pull
Определите сообщение атома с сильной типизацией в 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;
}