
Perfetto — крутейший инструмент. Он покажет вам те проблемы с производительностью, которые не заметит другой профайлер.
Perfetto покажет, что процессор занят системными задачами, когда ваш поток готов работать. Подсветит, что GC блокирует UI на 50 миллисекунд. А ещё расскажет, что именно планировщик ядра выкидывает поток с CPU.
Привет! Меня зовут Андрей Гришанов. В этой статье я расскажу, что такое Perfetto и как использовать его максимально эффективно.
Вы узнаете, как записать трейс холодного старта приложения и написать SQL-запрос для поиска дропнутых кадров. А ещё я покажу, как автоматизировать весь процесс в bash-скрипт, который выдаёт готовый отчёт за 15 секунд.
Немного истории: что такое Perfetto и откуда он взялся
Perfetto — это не очередной инструмент профилирования, а полноценная экосистема трассировки производственного уровня. Google разработал её в 2017-2018 годах, чтобы заменить устаревшие systrace и chrome://tracing на Android и в браузере Chrome.
В отличие от предшественников Perfetto был спроектирован как система следующего поколения. Упор в ней делался на масштабирование — от локальной отладки на одном устройстве до анализа трейсов с тысяч устройств в тестовых лабораториях Google.
Perfetto стал доступен в Android 9, а уже в Android 11 стал стандартной системой трассировки по умолчанию. Если вы использовали System Tracing в Android Studio версии 2021 года или новее, под капотом уже работал Perfetto.
Какую проблему решает Perfetto?

Представим типичную ситуацию: пользователи жалуются на лаги, вы открываете профайлер, а графики... выглядят нормально. Проблема тут в том, что стандартный профайлер показывает только ваш код: методы, память и использование CPU именно вашего процесса.
Да только ваше приложение работает не в вакууме. Оно конкурирует за ресурсы с десятками системных процессов, зависит от планировщика ядра Linux и может терять процессорное время, даже когда ваш код идеален.
Perfetto решает эту проблему, объединяя три источника данных на одной временной шкале:
ftrace (ядро Linux) — когда ваш поток реально выкинули с процессора (событие sched_switch);
atrace (Android Framework) — события measure, layout, draw от системы;
Пользовательские трейсы — добавленные разработчиком трейсы бизнес-логики приложения.
Это как в чёрном ящике самолёта. Вы видите и действия пилота (кастомные трейсы), и показания датчиков системы: ветер, высоту, температуру двигателей (аtrace и ftrace). Вот как это может выглядеть:

Кастомные трейсы
Они же — пользовательские трейсы. Это незаменимые помощники в изучении проблем с производительностью.
Пока ftrace и atrace отражают системную картину, кастомные трейсы показывают, когда та или иная операция прошла внутри приложения: «тут загружаются данные из API, а тут — парсится JSON на 5000 элементов». Чтобы добавить их в приложение, нужна одна зависимость:
dependencies {
implementation("androidx.tracing:tracing-ktx:1.3.0-alpha02")
}Теперь оборачиваем нужные участки кода:
import androidx.tracing.trace
class MyViewModel {
fun loadData() {
trace("MyViewModel.loadData") {
val data = repository.fetchData()
processData(data)
}
}
private fun processData(data: List<Item>) {
trace("MyViewModel.processData") {
data.map { transform(it) }
}
}
}Готово! В трейсе вы увидите слайсы с точным временем выполнения каждого блока. Например, если processData занимает 150 мс, а Main Thread в это время проводит 80 мс в состоянии Runnable и ждёт CPU, понятно, что половину времени поток простоял из-за нехватки процессорного времени, а не из-за медленного кода.
Оверхед: в продакшене этот код ничего не стоит: ~3-5 наносекнуд на проверку, активна ли запись. Смело оборачивайте все тяжёлые операции в приложении!
Бонус: интеграция с Firebase Performance
Кстати, кастомные трейсы можно не только локально анализировать через Perfetto. Вы можете и отправлять их в Firebase для мониторинга продовских сборок через Firebase Performance SDK:
dependencies {
implementation("com.google.firebase:firebase-perf:20.5.2")
}Используйте тот же синтаксис trace(), и метрики автоматически попадут во вкладку Firebase Performance в консоли Firebase. Там вы увидите агрегированную статистику по всем пользователям: медианное время выполнения, 90-й перцентиль, количество срабатываний. Вот, например, как выглядит метрика startup_init_app_scope, которую я показывал на скриншоте ранее:

Это особенно полезно для отслеживания регрессий после релизов. Если медианное время loadData() выросло с 250 мс до 800 мс после обновления, вы узнаете об этом из дашборда, а не из гневных отзывов в Google Play.
Возвращаемся к главному: мы познако��ились со всеми трёмя источниками данных: ftrace от ядра, atrace от Android и кастомными трейсами. Теперь разберёмся, как их собрать в один файл трейса, и что он вообще из себя представляет.
Как это работает: анатомия трейса
Файл .perfetto-trace содержит два типа данных:
Duration Events (действия с длительностью):
метод
onDrawвыполнялся 16 мс;слайс
activityResumeдлился 300 мс;GCработал 45 мс.
Instant Events (мгновенные снимки состояния):
частота CPU упала до 1.8 ГГц в момент времени T;
поток Main находится в состоянии Runnable (готов работать, но ждёт CPU);
температура процессора 45°C.
Perfetto не только показывает, что «UI лагает», но и из-за чего это происходит: «запустилась анимация (ваш код), но в эту же миллисекунду системный процесс kworker загрузил CPU на 80%, поэтому ваш поток провел 50 мс в состоянии Runnable вместо Running».
Три способа записать трейс
1. Android Studio Profiler
Самый простой способ для начала. Кликаете по кнопке записи, взаимодействуете с приложением, останавливаете. Файл можно открыть в Chrome в интерфейсе ui.perfetto.dev.
Плюсы:
не требует настройки — работает из коробки;
знакомый интерфейс для тех, кто уже использовал Android Studio;
файл автоматически сохраняется локально.
Минусы:
ограниченные настройки записи — не все категории ftrace доступны;
фиксированный размер буфера;
нужно подключение к компьютеру.

2. System Tracing App
Встроенное приложение в меню разработчика Android. Запускаете запись, тестируете приложение, останавливаете — файл сохраняется на устройстве.
Плюсы:
работает без компьютера — можно записать трейс на любом устройстве;
идеально подходит для воспроизведения проблем в полевых условиях: можете дать инструкцию пользователю, а он запишет трейс на своём лагающем устройстве;
больше настроек, чем в Android Studio Profiler.
Минусы:
менее удобен для систематического анализа;
нужно скачивать или отправлять трейс иными путями туда, где будем его анализировать.

Perfetto CLI — любовь с первого запроса
Perfetto CLI — это работа через терминал на компьютере с утилитой perfetto, которая находится на Android-устройстве (/system/bin/perfetto). Вы создаёте конфигурационный файл локально, загружаете его на устройство через adb push, а затем запускаете запись командой adb shell perfetto -c путь_к_конфигу.
Плюсы:
полный контроль над всеми параметрами записи;
воспроизводимые конфигурации для CI/CD;
можно запускать программно из скриптов;
точная настройка источников данных и размера буфера.
Минусы:
требует понимания конфигурационного формата;
более высокий порог входа.
Просто перечислить плюсы и минусы Perfetto CLI — проявить к нему неуважение. Если Android Studio Profiler — это наушники с преднастроенным звуком от производителя, то CLI — это наушники с 15-полосным эквалайзером. Это выбор настоящих ценителей гибкой настройки, CI/CD пайплайнов и выверенного контроля над тем, что и когда происходит.
Анатомия конфига: из чего он состоит
Конфигурационный файл описывается в формате protobuf text обычно с расширением .pbtxt. Его структура достаточно простая — два главных блока.
Блок 1: Буферы (buffers), где хранятся события в памяти
Perfetto использует кольцевые буферы: когда буфер заполняется, старые данные перезаписываются новыми. Это позволяет записывать длинные трейсы без риска переполнить память.
buffers: {
size_kb: 65536 # 64 МБ
fill_policy: RING_BUFFER # Или DISCARD (остановить при заполнении)
}Для холодного старта 64 МБ обычно достаточно. Если нужно записать 30+ секунд активного использования — увеличиваем до 128-256 МБ.
Блок 2: Источники данных (data_sources) — что именно записывать
Perfetto поддерживает десятки источников. Самые полезные для Android-разработчика:
linux.ftrace— события ядра Linux: переключения потоков, частоты CPU;atraceкатегории — события Android Framework: AM, View system, Graphics;linux.process_stats— статистика процессов: CPU, память;android.heapprofd— профилирование native памяти.
Минималистичный конфиг для начала
Начнем с простого конфига для записи холодного старта. Создадим файл startup.pbtxt на ПК:
buffers: {
size_kb: 65536
fill_policy: RING_BUFFER
}
data_sources: {
config {
name: "linux.ftrace"
ftrace_config {
# События планировщика
ftrace_events: "sched/sched_switch"
# Частота процессора
ftrace_events: "power/cpu_frequency"
# Android Framework
atrace_categories: "am" # Activity Manager
atrace_categories: "view" # View system
atrace_categories: "dalvik" # ART VM
# Только ваше приложение
atrace_apps: "com.your.app"
}
}
}
duration_ms: 15000 # 15 секундЭтот конфиг включает минимальный набор для анализа старта: планировщик, частоты CPU и основные категории Android. Perfetto запишет 15 секунд и автоматически остановится.
Как использовать:
# 1. Загружаем конфиг на устройство
adb push startup.pbtxt /data/local/tmp/
# 2. Запускаем запись
adb shell perfetto \
-c /data/local/tmp/startup.pbtxt \
-o /data/misc/perfetto-traces/trace.perfetto-trace
# 3. Скачиваем результат
adb pull /data/misc/perfetto-traces/trace.perfetto-tracePerfetto сохранит файл, можно открывать на ui.perfetto.dev.
Когда нужно больше данных
Если минимального конфига недостаточно, добавим дополнительные источники. Вот что можно включить для разных задач:
Для глубокой диагностики лагов UI добавьте в ftrace_config:
ftrace_events: "sched/sched_wakeup" # Когда потоки просыпаются
ftrace_events: "power/cpu_idle" # Состояния сна CPU
atrace_categories: "gfx" # SurfaceFlinger
atrace_categories: "input" # Тапы/свайпы
atrace_categories: "sync" # Синхронизация потоковИ увеличьте буфер до 128 МБ (size_kb: 131072). Так уместим больше событий.
Для профилирования памяти нужно добавить новый data_source и увеличить буфер до 256 МБ (size_kb: 262144), потому что heap dumps занимают много места:
data_sources: {
config {
name: "android.heapprofd"
heapprofd_config {
sampling_interval_bytes: 4096
process_cmdline: "com.your.app"
continuous_dump_config {
dump_interval_ms: 10000 # Снимок каждые 10 сек
}
}
}
}Для мониторинга нагрузки процессов добавляем:
data_sources: {
config {
name: "linux.process_stats"
process_stats_config {
scan_all_processes_on_start: true
proc_stats_poll_ms: 1000 # Опрос каждую секунду
}
}
}Главное правило: не включайте всё подряд. Каждый источник увеличивает размер трейса и вносит оверхед. Лучше начать с минимального набора, а если проблема не определяется — расширьте зону поиска.
Короткий синтаксис для экспериментов
Если не хочется создавать файл для быстрой проверки, есть короткий синтаксис:
adb shell perfetto \
-o /data/misc/perfetto-traces/trace.perfetto-trace \
-t 20s \ # Длительность
-b 64mb \ # Размер буфера
--app com.your.app \ # Ваше приложение
sched freq am view gfx dalvik # Категории через пробелЭто эквивалентно конфигу, но написано в одну строку. Удобно для одноразовых экспериментов.
Фоновая запись для длинных сценариев
Если нужно записать трейс, пока вы выполняете длинную последовательность действий:
# Запускаем в фоне (Perfetto выведет PID)
adb shell perfetto \
-c /data/local/tmp/config.pbtxt \
-o /data/misc/perfetto-traces/trace.perfetto-trace \
--background
# Сохраняем PID, например: 12345
# ... выполняете действия в приложении ...
# Останавливаем по PID
adb shell kill -TERM 12345
# Ждем завершения записи
sleep 2
# Скачиваем
adb pull /data/misc/perfetto-traces/trace.perfetto-traceСправочник. Полезные atrace категории:
Категория | Что показывает | Когда использовать |
am | Activity Manager | Старт приложения, lifecycle |
wm | Window Manager | Работа с окнами, layout |
view | View system | measure/layout/draw |
gfx | SurfaceFlinger | Композиция кадров |
input | Input events | Лаги при тапах |
dalvik | ART VM | GC, JIT-компиляция |
database | SQLite | Медленные запросы к БД |
network | Network stack | Сетевые запросы |
camera | Camera HAL | Проблемы с камерой |
bionic | C library | malloc, threading |
Полный список: adb shell atrace --list_categories
Важные детали
На Android 9-10 (если у вас не Pixel): может потребоваться
adb shell setprop persist.traced.enable 1перед первым использованием.Размер трейса: 1 минута полной записи ≈ 50-200 МБ в зависимости от активности.
Root не нужен: Perfetto работает на нерутованных устройствах с Android 9+.
Где класть конфиг: на Android 12+ можно использовать /data/misc/perfetto-configs/ для обхода некоторых SELinux ограничений
SQL-анализ: главная киллер-фича Perfetto
Perfetto запускает собственный SQL-движок trace_processor (документация) с синтаксисом, похожим на SQLite. Вместо того, чтобы часами скроллить таймлайн в поисках лага, вы задаёте вопрос базе данных.
Кейс 1. Точное время холодного старта
Вместо приблизительных оценок, считаем миллисекунды от bindApplication до первого activityResume:
SELECT
(SELECT ts FROM slice WHERE name LIKE '%activityResume%' LIMIT 1) -
(SELECT ts FROM slice WHERE name = 'bindApplication' LIMIT 1)
AS startup_ns;Результат: 1234567890 наносекунд → делим на 1e6 → получаем время в миллисекундах.
Например, если использовать ui.perfetto.dev, то этот процесс будет выглядеть так:

Кейс 2. Дропнутые кадры (Jank)
Находим все кадры Choreographer#doFrame, отрисовка которых заняла больше 16,6 мс:
SELECT
ts/1e9 as time_sec,
dur/1e6 as frame_ms,
name
FROM slice
WHERE name LIKE 'Choreographer#doFrame%'
AND dur > 16.6e6
ORDER BY dur DESC;Получаете таблицу. Она показывает, на какой секунде произошёл лаг, и сколько он длился:

Кейс 3. Поток готов, но процессор занят
Самая частая причина «необъяснимых» лагов — состояние потока. В таблице thread_state видно разницу:
Running — поток реально исполняется на ядре;
Runnable — поток готов работать, но ждёт очереди (CPU занят);
Uninterruptible Sleep (D) — поток заблокирован (обычно I/O операции).
Запрос для поиска моментов, когда Main Thread долго ждал CPU:
SELECT
ts/1e9 as time_sec,
dur/1e6 as wait_ms,
state
FROM thread_state -- Из таблицы thread_state (история состояний потоков)
-- ПОДЗАПРОС: находит utid главного потока приложения
WHERE utid = (
SELECT t.utid
FROM thread t
JOIN process p USING(upid)-- Объединяем с таблицей process по upid
WHERE p.name = 'ru.dodopizza.app.beta' -- Ищем процесс вашего приложения
AND t.name LIKE '%pizza.app.beta%'-- Ищем поток, имя которого содержит часть имени пакета
LIMIT 1
)
AND state = 'R' -- Runnable
AND dur > 10e6 -- Больше 10 мс
ORDER BY dur DESC; -- Сортируем В UI-интерфейсе Perfetto это выглядит так:

Чтобы увидеть общую картину и оценить масштаб проблемы, стоит выполнить поиск SQL-запросом. Так вы получите отфильтрованный список всех проблем.

Если таких моментов много, оптимизировать ваш Kotlin-код бесполезно. Ищите, что загружает CPU. Это могут быть другие процессы или фоновые задачи системы.
Автоматизация: скрипт вместо ручной работы
Анализировать трейсы через веб-интерфейс удобно, если вы изучаете сложные баги. Если же вам нужно быстро проверить, не нагрузили ли вы GC доработками перед отправкой кода на ревью, ручной процесс вас утомит.
Однако, у Perfetto и тут есть отличное решение! Если вы всё ещё не начали им пользоваться, то сейчас я зайду с козырей.
Полный Bash-скрипт
https://gist.github.com/grishan0v/9b88c5a53e576f58534442f1ae1e8888
Не забудьте при запуске дать ему права на выполнение chmod +x
Что делает скрипт?
Убивает приложение (
am force-stop) для гарантии холодного старта.Запускает запись Perfetto в фоновом режиме на 15 секунд.
Включает atrace для вашего
package.Стартует приложение через monkey. Это встроенная в Android утилита для UI-тестирования. Команда
monkey -pcom.your.app-c android.intent.category.LAUNCHER 1отправляет ровно одно событие: запуск главнойActivityприложения. По сути, это программная имитация тапа пользователя по иконке приложения на рабочем столе.Скачивает трейс с устройства.
Анализирует через
trace_processorSQL-запросы к файлу без браузера.Генерирует отчёт
report.txtс готовыми цифрами.
Вместо 15 минут ручной работы — 15 секунд ожидания.
Шаг 1: Установка trace_processor
# Download prebuilts (Linux and Mac only)
curl -LO https://get.perfetto.dev/trace_processor
chmod +x trace_processorШаг 2: Как работает SQL-анализ в скрипте
Ключевой фрагмент — функция выполнения SQL через pipe:
TRACE_PROCESSOR="./trace_processor"
LOCAL_TRACE="trace.perfetto-trace"
REPORT_FILE="report.txt"
# Функция выполнения SQL
run_sql() {
echo "$1" | "$TRACE_PROCESSOR" "$LOCAL_TRACE" 2>/dev/null
}
# Пример использования
echo "--- Анализ Jank-фреймов (>16.6мс) ---" >> $REPORT_FILE
SQL_JANK="SELECT
COUNT(*) as bad_frames
FROM slice
WHERE name LIKE 'Choreographer#doFrame%'
AND dur > 16666666;"
run_sql "$SQL_JANK" >> $REPORT_FILEКак это работает:
Вы передаете SQL-запрос через
echoвtrace_processor.Утилита выполняет запрос и возвращает результат в формате CSV (
comma-separated values)bad_frames 54Этот CSV-вывод дописывается в конец файла report.txt (оператор
>>в Bash).
Файл report.txt содержит обычный текст + CSV-таблицы от trace_processor. Вы можете открыть его в любом текстовом редакторе или импортировать в Excel/Google Sheets.
Что вы получаете на выходе
Итоговый вид файла report.txt ниже. Если процент janky_frames вырос с 2% до 5% после вашего коммита, — вы узнаете об этом мгновенно.
--- СТАТИСТИКА ФРЕЙМОВ ---
total_frames,janky_frames,janky_percent
1200,54,4.5
--- ТОП-5 САМЫХ МЕДЛЕННЫХ ФРЕЙМОВ ---
time_sec,duration_ms
4.231,82.10
5.105,45.20
7.891,38.50
...
--- ДОЛГИЕ GC (>20мс) ---
name, duration_ms
CollectorType GC, 52.3
HeapTrim, 31.8Интеграция в CI/CD
Следующий шаг — автоматическая проверка производительности в пайплайне. После прогона UI-тестов в CI:
Скрипт записывает трейс.
Анализирует его через SQL.
Сравнивает метрики с эталоном (baseline).
Если время старта увеличилось на 10%, билд помечается как нестабильный.
Упрощённый пример проверки в CI (GitHub Actions). Он позволит ловить регрессии до того, как они попадут в main:
- name: Performance Check
run: |
./perfetto_record.sh
STARTUP_MS=$(cat report.txt | grep "startup_ms" | cut -d',' -f2)
BASELINE_MS=850
if [ $STARTUP_MS -gt $((BASELINE_MS * 110 / 100)) ]; then
echo "❌ Регрессия производительности: $STARTUP_MS мс (baseline: $BASELINE_MS мс)"
exit 1
fiБонус: AI-агенты для анализа трейсов
Рубрика «Заставляем ИИ делать работу за нас»: вместо написания SQL вручную, используем большие языковые модели (LLM) для генерации запросов.
Как это работает:
Получаем схему базы данных Perfetto — список всех таблиц и их колонок. Это можно сделать командой:
echo ".schema" | trace_processor trace.perfetto-traceВывод будет примерно таким:
CREATE TABLE slice(id INT, ts INT, dur INT, name TEXT, ...); CREATE TABLE thread(id INT, name TEXT, tid INT, ...); CREATE TABLE thread_state(id INT, ts INT, dur INT, state TEXT, ...);Передаём схему + задачу в LLM (например, ChatGPT, Claude, Gemini):
Ты — эксперт по анализу Perfetto трейсов. Вот схема базы данных: {schema} Задача: Найди в трейсе все моменты, где Main Thread был в состоянии Runnable (ждал CPU) дольше 50 мс. Выведи SQL-запрос и объясни, что может быть причиной.LLM генерирует SQL и объясняет логику:
SQL-запрос: SELECT ts/1e9 as time_sec, dur/1e6 as wait_ms, (SELECT name FROM thread WHERE id = thread_state.utid) as thread_name FROM thread_state WHERE state = 'R' AND dur > 50000000 AND thread_name = 'main' ORDER BY dur DESC; Объяснение: Состояние Runnable означает, что поток готов выполняться, но ждет освобождения CPU. Возможные причины: - Высокая нагрузка от других процессов - Фоновые задачи системы - Недостаточная мощность процессора на устройствеВыполняем SQL — здесь два варианта:
Вариант A. Вручную через trace_processor: копируете SQL от LLM и запускаете сами. Полный контроль над процессом.echo "SELECT ... FROM thread_state ..." | trace_processor trace.perfetto-traceВы копируете SQL от LLM и запускаете сами. Полный контроль над процессом.
Вариант Б. LLM сама выполняет запрос
Современные LLM могут сами запустить trace_processor и получить результаты. Вы просто загружаете трейс и пишете:
«Проанализируй трейс (путь к трейсу), найди все моменты, где
Main Threadждал CPU больше 50 мс, и объясни, что происходило. Используйtrace_processor(путь к нему, если LLM запускается не в том же пакете)»LLM сама сгенерирует SQL, выполнит его через trace_processor и проанализирует результаты.

Это особенно полезно для быстрого старта поиска проблемы. Вместо того, чтобы вручную изучать схему таблиц и писать запросы методом проб и ошибок, вы сразу получаете рабочую гипотезу и SQL для её проверки. Подходит как для быстрого прототипирования сложных аналитических запросов, так и для первичной диагностики незнакомых проблем производительности.
Что дальше
Perfetto превращает профайлинг из шаманства в инженерную дисциплину. Вы оперируете фактами с точностью до наносекунд, а не догадками.
С чего начать:
Запишите System Trace проблемного сценария в вашем приложении.
Откройте файл на ui.perfetto.dev.
Попробуйте выполнить один SQL-запрос из этой статьи.
Адаптируйте скрипт под свой проект и пользуйтесь по необходимости.
Полезные ссылки:
Если вы используете Perfetto, расскажите в комментариях, как он решает ваши проблемы и помогает в разработке? Как часто вы используете его? Стал ли Perfetto одним из этапов разработки фичей?
А на этом всё. Спасибо, что дочитали статью! Чтобы оставаться в курсе последних новостей нашей команды, подпишитесь на Telegram-канал Dodo Engineering. В нём много клёвого контента о нашей команде, продуктах и культуре!
