В какой-то момент разработки приложение становится достаточно зрелым, а команда достаточно большой. Или наоборот. Тогда кто-то решает, что пора собирать метрики: Crash Rate, Hitch Rate, время холодного запуска, потребление памяти, сетевые ошибки. Организуются красивые дашборды, пишутся аккуратные отчеты, ставятся четкие квартальные цели.

Все довольны. Пока не придет технический директор и не спросит:

А чего это у нас так устройства греются?

С чего начать

Для начала можно задать простые вопросы:

  • Сколько процентов пользователей сталкиваются с перегревом устройства?

  • Какие модели греются чаще остальных?

  • На каких экранах растёт температура?

  • Влияет ли перегрев на крэши и FPS?

При анализе нагрузки команды чаще смотрят на использование CPU/GPU или памяти, и реже - на тепловое состояние устройства. В iOS это оценку можно получить с помощью параметра ProcessInfo.ThermalState. Значения ThermalState могут использоваться как локальный сигнал, чтобы выключить какой-нибудь блюр или остановить фоновые операции, а потом ответить на вопрос «Умеет ли приложение реагировать на перегрев?».

Но если собирать ThermalState как продуктовую метрику, то можно получить куда более интересную информацию: насколько часто и где пользователи вообще с этим сталкиваются.

Что это такое

Миф: ThermalState - это температура телефона.

Правда: iOS вообще не даёт публичного API для чтения температуры процессора или аккумулятора. Вместо градусов система сообщает уровень теплового давления (thermal pressure) - интегральную оценку, причем Apple не раскрывает, какие алгоритмы и параметры использует.

Подробнее

То есть это не физическое измерение, а решение операционной системы: «устройству сейчас плохо вот настолько». Насколько? Есть 4 варианта:

State

Формулировка Apple

Что это означает

nominal

«within normal limits»

устройство работает в нормальном режиме

fair

«slightly elevated»

начинается нагрев, пора задуматься

serious

«high»

система уже ограничивает производительность

critical

«significantly impacting the performance… the device needs to cool down»

нужно быстро снижать нагрузку

Уже на serious система начинает гасить I/O операции, геолокацию, FPS. На critical может отключить периферию: камеру, микрофон, динамик.

Эта метрика хорошо подходит для аналитики, так как она уже нормализована между устройствами. serious на iPhone SE и serious на iPhone 15 Pro означают одно и то же: «системе тяжело».

Почему не CPU Usage

В профилировщике мы увидим, что операция заняла 100ms. Или 2 Gc CPU на 3 ядрах. И что дальше?

  • Это безопасно или нет?

  • Устройство уже перегревается или пока всё нормально?

  • Пользователь вообще что-то ощущает?

Если CPU Usage - причина проблемы, то ThermalState - следствие:

Количество негативных отзывов растет, при этом по обычным метрикам всё может быть нормально (кроме оценки в сторе 🙃). И отдельный замер CPU может ввести в заблуждение. Высокий CPU Usage при nominal в, например, видеоредакторе приемлем, работа с видео и должна грузить процессор. А вот высокий CPU Usage при serious уже явный повод насторожиться, потому что это ведет к деградации пользовательского опыта. Без ThermalState трудно отличить одно от другого.

Техническая часть

Работы немного. Текущее состояние читается одной строкой (свойство thermalState):

let state = ProcessInfo.processInfo.thermalState

Изменения приходят через нотификацию (thermalStateDidChangeNotification):

NotificationCenter.default.addObserver(
    forName: ProcessInfo.thermalStateDidChangeNotification,
    object: nil,
    queue: .main
) { _ in
    let newState = ProcessInfo.processInfo.thermalState
    Analytics.track(thermalState: newState)
}

Но, как говорится, есть один нюанс. Чтобы получать уведомления об изменениях, нужно сначала прочитать свойство thermalState и только потом регистрировать наблюдателя, иначе нотификации могут просто не приходить. Про это важно помнить, чтобы первый эксперимент не провалился и затея не ушла в стол.

Ещё два практических замечания:

  1. Самостоятельно логгируйте состояние в ключевых точках жизненного цикла приложения: старт сессии, вход на «тяжёлые» экраны, начало и конец долгих операций. Это поможет сохранить контекст вокруг изменений состояния.

  2. Проверяйте на реальных устройствах. Если система не может определить тепловое состояние или железо не поддерживает thermal awareness, свойство возвращает nominal. Физически греть устройство не нужно: начиная с Xcode 11 есть Device Conditions, позволяющие искусственно выставить любой thermal state на подключённом устройстве.

Дальше начинается интересное.

Какие события отправлять

При получении сигнала о проблеме желательно иметь хотя бы какой-то контекст. Пример более-менее осмысленного события:

{
    "thermal_state": "serious",
    "device": "iPhone 14",
    "ios": "18.5",
    "screen": "VideoEditor",
    "session_duration": 1320,
    "battery_level": 41,
    "low_power_mode": false,
    "charging": true
}

Каждое поле здесь содержит важную информацию:

  • device - какие модели страдают. Старые и/или маленькие устройства рассеивают тепло хуже.

  • screen - какие фичи греют устройство.

  • session_duration - как быстро пользователи доходят до нагрева.

  • charging - зарядка (даже беспроводная) сама по себе греет устройство. В iOS есть механизм, при котором система замедляет или ставит зарядку на паузу из-за температуры. Поможет отсеять случаи, когда проблема не в коде (устройство на зарядке в машине летом само уйдет в fair).

  • battery_level и low_power_mode - состояние батареи / режим энергосбережения влияют и на нагрев, и на троттлинг.

Потом можно расширять события: тип операции (камера, инференс ML-модели, экспорт видео, рендер), текущий FPS, потребление памяти.

А ещё их необязательно сэмплировать, потому что изменения thermal state - события сравнительно редкие, и вряд ли они забьют канал аналитики.

Дашборды

Графики нужно строить осознанно, каждый из них должен отвечать на конкретный вопрос. Ниже приведены примеры графиков с условными цифрами (у нас нет публичных стандартов по этому параметру).

1. Насколько проблема вообще массовая? - общее распределение состояний

nominal    96.8%
fair        2.4%
serious     0.7%
critical    0.1%

0.7% сессий в serious при десяти миллионах сессий в месяц - это 70 тысяч сессий, в которых система снижала производительность приложения. Не так уж и мало.

2. Кому тяжелее всего? - разбивка по моделям устройств

iPhone SE 3     ██████████
iPhone 11       ██████
iPhone 14       ███
iPhone 15 Pro   ██

Как правило, для маленьких и старых устройств строки будут длиннее. Так получается список устройств, для которых имеет смысл программно снижать нагрузку.

3. Какие фичи греют устройство? - разбивка по экранам

Камера         ████████
Экспорт видео  ███████
Лента          ██
Настройки      █

Это самый информативный для разработчиков график. Если 80% переходов в serious происходят на двух экранах, то у вас уже есть приоритизированные задачи по оптимизации.

4. Как быстро пользователь доходит до деградации? - время до первого serious

Медиана: 18 минут

Метрика особенно ценна в динамике. По сути, это опережающий индикатор. Если после релиза медиана упала с 18 до 8 минут, то у вас проблемы, даже если Crash Rate и Hitch Rate пока выглядят нормально.

5. Не сломал ли что последний релиз? - сравнение версий приложения

5.2.0   serious rate: 0.4%
5.3.0   serious rate: 1.8%

Причина роста может быть любая: чей-то новый шейдер, забытый таймер или ML-модель, которую стали дёргать чаще. С помощью этого графика при поэтапной раскатке можно оценить, стоит ли катить новую версию дальше.

6. Как нагрев влияет на количество крэшей?

Считаем Crash Rate отдельно для сессий, в которых был serious/critical, и для остальных. Если разница есть, то перегрев может быть косвенной причиной проблем. Также этот срез может объяснить некоторые «странные» крэши, которые не воспроизводятся ни на одном тестовом устройстве, потому что тестовые устройства лежат на столе холодными.

7. Как нагрев влияет на отрисовку/скролл?

Если вы уже собираете FPS, наложите на него thermal state. Сама по себе получившаяся корреляция ожидаема и открытием не станет. Зато она поможет повысить приоритет задачи по оптимизации, потому что «пользователи при большом нагреве видят 38 FPS вместо 60».

Что делать с этими данными

Собранная аналитика превращает абстрактное «надо бы оптимизировать» в конкретные задачи.

Пример 1: точечная оптимизация

Дашборд показывает, что 85% переходов в serious происходят на экране Camera после 12 минут непрерывной работы. Это повод сделать четыре вещи:

  • снизить частоту обработки кадров в превью;

  • реже запускать ML-модель (например, инференс не на каждом кадре, а на каждом третьем);

  • уменьшить разрешение предпросмотра;

  • ограничить количество одновременно активных фильтров.

После релиза возвращаемся к графику «время до первого serious» и смотрим, сдвинулась ли медиана.

Пример 2: адаптация под конкретные устройства

Данные показывают, что до critical регулярно доходит только линейка iPhone SE. Тогда включаем адаптивный режим только там, где он реально нужен, и не ухудшаем пользовательский опыт всем остальным пользователям.

Пример 3: реактивная деградация в рантайме

Подписались на нотификацию, при serious снизили качество рендера, при critical поставили тяжёлые операции на паузу. Данные покажут, помогает ли это (уменьшилась ли доля critical после включения) и не деградируете ли вы опыт слишком рано.

Итого

ProcessInfo.ThermalState обычно живёт в коде как одноразовый if: пришло serious - выключили анимацию. Это полезно, но неоптимально. Хотя iOS уже вычисляет интегральную оценку теплового состояния каждого устройства каждого пользователя, остаётся только начать её записывать.

Стоимость внедрения маленькая: одно свойство, одна нотификация, одно событие в аналитике. А взамен команда получает ответы на вопросы, которые раньше даже не звучали: сколько пользователей перегреваются, где именно, на каких устройствах, и не стало ли хуже после последнего релиза.