Всем привет, на связи Тимур, senior Android-разработчик с более чем 5 годами опыта. Сейчас работаю в платформенной команде продуктовой компании — мы отвечаем за стабильность, производительность и всё то, что “никто не замечает, пока всё работает, и все замечают, когда перестаёт”.

В какой-то момент простого дашборда уже не хватает. Хочется, чтобы метрики не просто “где-то красиво лежали”, а сами приходили и били в лицо, когда что-то пошло не так.

В моей истории это был дашборд по перформансу (о нем думаю расскажу в следующих статьях), а пока давайте рассмотрим случай с Hitch Rate (лагучесть UI) в Android-приложении.

Данные летят в ClickHouse, поверх них — Grafana, и нужно:

  • посчитать 90 перцентиль "лагучести" по каждому экрану;

  • если метрика выше порога — слать алерт в Slack;

  • в сообщении сразу видеть: какие именно экраны плохие и с какими значениями.

На бумаге задача звучит как “ну сейчас за 15 минут сделаю”. В реальности я:

  • ломал голову над Unified Alerts;

  • ловил ошибки вроде only data source queries may be inputs to a classic condition;

  • получал пустые сообщения в Slack;

  • упирался в Invalid evaluation interval и странное поведение For / evaluation interval в rule groups.

В этой статье пройду по тому же пути, только аккуратно и без боли:

расскажу, как устроен Grafana Unified Alerting, чем отличаются queries и expressions, как собрать нормальное Slack-уведомление и где вас ждут грабли.

Что такое Hitch Rate и зачем он бизнесу

Если совсем по-простому:

  • приложение рисует кадры с частотой 60 FPS (или 90/120 на некоторых устройствах),

  • каждый кадр должен успеть отрендериться до следующего vsync,

  • если рендер занял слишком много времени — кадр «дёрнулся» или «подвис».

Hitch Rate — это доля таких проблемных кадров.

Например, если из 1000 кадров:

  • 920 — ок,

  • 80 — лаги,

то Hitch Rate = 80 / 1000 = 8%.

Можно думать так:

FPS отвечает за “сколько кадров в секунду”, а Hitch Rate — за “сколько из них были неприятными”.

Для бизнеса это тоже не абстракция:

  • Это прямая метрика UX-плавности: у экрана с Hitch Rate 3% ощущение «шёлковое», с 15% — “ну что-то подлагивает”.

  • Можно поставить простой SLA:

    «p90 Hitch Rate на ключевых экранах < 10%».

  • Дальше уже можно строить нормальную историю: «после оптимизации карточки товара Hitch Rate с 18% упал до 7%, конверсия выросла на X%».

Короче, это цифра, за которую не стыдно спорить с бизнесом.

Осталось только научиться по ней алертиться.

1. Как вообще устроен Grafana Unified Alerting

Начиная с версии 8+ у Grafana единая модель алертов. Если раньше можно было жить в режиме “алерт у панели”, то сейчас всё крутится вокруг нескольких сущностей:

  • Alert rule — правило, которое:

    • выполняет один или несколько queries (запросов в БД / Prometheus / и т.д.),

    • опционально прогоняет результаты через expressions (Reduce, Math, Threshold),

    • выбирает один query или expression как condition (если он “true” → алерт Firing).

  • Rule group — набор правил, у которых:

    • общий evaluation interval (как часто считаться),

    • общая синхронизация (чтобы всё считалось в один тик).

    • Это как cron-job на уровне группы:

      “раз в минуту посчитать все performance-алерты”.

  • Contact point — куда слать уведомление (Slack, email, Telegram и т.п.).

  • Notification policy — кого и при каких лейблах уведомлять.

    • алерты с team = mobile → в Slack-канал #mobile-alerts;

    • алерты с env = prod → ещё и в #oncall.

И один важный момент, который ломает голову всем, кто привык к “один алерт = одно число”:

Один alert rule может создавать много alert instances

— по одному на каждую комбинацию label’ов (например, по одному на каждый экран).

Это не баг, а фича. Но к ней надо привыкнуть.

2. Query: считаем p90 Hitch Rate по экранам

Возьмём обобщённый ClickHouse-запрос.


Пусть у нас есть таблица metrics.mobile_events с JSON-полем event_json, где лежит Hitch Rate и название экрана.

В Grafana пишем Query A (упрощённо):

/* Hitch Rate p90 per screen */
SELECT
  screen,
  round(
    quantile(0.90)(
      toFloat64OrNull(JSON_VALUE(event_json, '$.hitch_rate'))
    ),
    2
  ) AS hitch_p90
FROM metrics.mobile_events
WHERE
  app = 'my.app.id'
  AND $__timeFilter(timestamp)
  AND event_type = 'PERFORMANCE'
  AND JSON_VALUE(event_json, '$.hitch_rate') IS NOT NULL
GROUP BY
  screen
HAVING
  hitch_p90 > ${hitch_threshold}  -- например, 5
ORDER BY
  hitch_p90 DESC;

В Grafana этот запрос (Query A) вернёт таблицу примерно такого вида:

screen

hitch_p90

MAIN

7

SPLASH

4

PROFILE

3

FAVORITES

1

Grafana превратит каждую строку в отдельную серию с label’ом:

  • screen="MAIN"

  • screen="SPLASH"

Это фундаментально важно:

Вы не получаете “одну метрику Hitch Rate”, вы получаете целый набор метрик — по одной на каждую комбинацию лейблов.

3. Почему алерт может «раздвоиться»: один rule — много инстансов

Теперь представим, что мы выбрали Query A как alert condition (без Expressions) и сказали:

“Если hitch_p90 > 10 — стреляй”.

Grafana делает следующее:

  • для каждой серии (screen="MAIN", screen="SPLASH"…) создаёт отдельный alert instance;

  • получается пачка алертов:

    • alertname = HighHitchRate, screen = MAIN

    • alertname = HighHitchRate, screen = SPLASH

    • alertname = HighHitchRate, screen = CART

    В UI это видно в View rule → State: там список инстансов с разными screen и значением Value.

    На этом шаге многие думают “что за дичь, я же хотел один алерт”. Но правда в том, что это очень удобно:

    • вы не получаете абстрактное “что-то плохо с Hitch Rate”;

    • вы видите конкретно: “плохо с MAIN, SPLASH и CART”.

    4. Queries vs Expressions: что за B, C и откуда ошибки

    В Unified Alerting есть два основных сценария:

    1. Простой:

      • Только Query A, без Expressions.

      • Alert condition = A.

      • Классика: “если A > threshold — алерт”.

      • Это похоже на “старую” Grafana: одно значение → одно условие.

    2. С выражениями (Expressions):

      • Query A — данные.

      • Expression B (Reduce) — считаем что-то поверх A (count, max, avg и т.д.).

      • Expression C (Threshold) — проверяем условие (например, B > 0).

      • Alert condition = C.

      • Пример сценария:

        • A возвращает hitch_p90 по всем экранам;

        • B считает Series count — сколько экранов превысили порог;

        • C проверяет: “если count > 0 → алерт”.

    Типичные грабли:

    • only data source queries may be inputs to a classic condition, B is a Expression

      → вы пытаетесь использовать Classic condition на Expression, а он понимает только raw queries;

    • failed to parse expression 'B': invalid math command type: expr: non existent function A

      → в выражении обращаетесь к A как к функции, а не как к входу.

    Мораль простая:

    • либо живём только с Query A и делаем его condition;

    • либо строим чёткую цепочку A → B → C и помечаем C “Set as alert condition”;

    • classic condition при этом обычно вообще не нужен.

    5. Slack: почему шаблон даёт пустоту или мусор

    Второй набор граблей — шаблоны сообщений.

    5.1. Контекст шаблона в Grafana-managed alerts

    Для Grafana-управляемых алертов (Unified Alerting) в Slack-шаблоне доступны, например:

    {{ range .Alerts }}
      {{ .Status }}                 // firing, resolved
      {{ .Labels }}                 // карта лейблов
      {{ .Annotations }}            // description, summary и т.д.
      {{ .ValueString }}            // уже красиво отформатированное значение
    {{ end }}Типичный рабочий шаблон:

    Типичный рабочий шаблон:

    High Hitch Rate Alert
    
    Проблемные экраны (p90 > {{ .Annotations.threshold }}%):
    
    {{ range .Alerts }}
    - {{ index .Labels "screen" }} — p90 hitch = {{ .ValueString }}
    {{ end }}

    Распространённые ошибки:

    • Использовать {{ .Alerts.Firing }} или .Matches — это синтаксис Alertmanager, а не Grafana-managed alerts → в ответ приходит пустота.

    • Использовать {{ .Values }} — и получать страшный вывод вида:

    [ var='A' labels={screen=MAIN} value=7 ], [ var='B' labels={screen=MAIN} value=1 ]
    • потому что .Values — это “сырые значения всех series”, а не итог алерта.

    5.2. Как красиво вывести именно экраны

    Тот же запрос A с screen в лейбле даёт нам удобный шаблон:

    :rotating_light: High Hitch Rate Alert
    
    Проблемные экраны (p90 > {{ .Annotations.threshold }}%):
    
    {{ range .Alerts }}
    - {{ index .Labels "screen" }} — p90 hitch = {{ .ValueString }}
    {{ end }}

    Если у screen другое имя (например, metric), просто подставляем его.

    6. Почему значение выглядит как 1e+00, и что с этим делать

    В UI и в уведомлениях иногда можно увидеть что-то вроде:

    • Value = 1e+00

    • Value = 2.2e+00

    Это просто формат float в научной нотации.

    Чтобы не форматировать руками, лучше использовать:

    {{ .ValueString }}

    а не .Value.

    7. Rule groups, evaluation interval и ошибка “Invalid evaluation interval”

    Ещё один “приятный сюрприз”:

    Invalid evaluation interval. Evaluation interval should be smaller or equal to 'For' values for existing rules in this group.

    Что происходит:

    • правила объединены в rule groups;

    • у группы есть evaluation interval — например, 1 минута;

    • у каждого правила есть For — сколько времени условие должно быть true, прежде чем алерт станет Firing.

    Ограничение:

    evaluation interval ≤ For для всех правил в группе.

    Пример:

    • у группы mobile-performance стоит evaluation interval = 1m;

    • вы создаёте новое правило с For = 30s;

    • получаете ошибку.

    И наоборот:

    • если хотите правило, которое считается раз в неделю (evaluation interval = 7d),

    • то либо оно живёт в отдельной группе,

    • либо For у него не может быть меньше 7 дней.

    Метафора:

    Rule group — это общий “метроном”.

    Все правила в группе обязаны шагать в такт с ним.

    8. Как протестировать алерт и Slack, не дожидаясь “раз в неделю”


    Пара практических лайфхаков:

    1. Временно уменьшить evaluation interval и For

      • Поставить eval interval, например, 1m.

      • Поставить For = 0m (или маленькое значение).

      • Немного подождать, посмотреть, что rule переходит в Alerting, и прилетает нотификация.

    2. Использовать Preview в редакторе правила

      • В разделе alert rule есть вкладка Preview.

      • Она показывает, вернёт ли ваш query/condition сейчас firing/ok и какие значения.

    3. Сделать временный тестовый rule

      • С тем же запросом,

      • но более жёстким/мягким порогом, чтобы гарантированно получить Firing,

      • и отдельным Slack contact point.

    Так можно спокойно отладить шаблон и формат сообщения, не трогая боевой алерт.

    9.Итог: с какой мыслью хочется уйти

    Grafana Unified Alerting на старте ощущается как:

    “Я просто хотел, чтобы мне в Slack приходила цифра,

    а в итоге изучаю маленький оркестр с дирижёром, группами и собственной DSL”.

    Но если принять три опорные идеи:

    1. Query может вернуть много серий → это нормальный способ сказать “у тебя много объектов (экранов/подов/серверов)”.

    2. Expressions — это просто ещё один слой вычислений поверх query, а не какая-то магия.

    3. В Slack-шаблоне нужно итерироваться по .Alerts и брать .Labels + .ValueString, а не пытаться тащить всё через .Values.

    — то Grafana Alerting перестаёт быть страшной, и превращается в очень удобный инструмент:

    • метрики продолжают “красиво лежать” на дашбордах;

    • а когда что-то пошло не так, они сами приходят и говорят “эй, посмотри сюда, тут Main-экран с p90 Hitch Rate 18%”.

    Если вы только начинаете разбираться с Unified Alerting — надеюсь, эта статья сэкономит вам несколько часов жизни, пару бессмысленных “почему Slack пустой?” и одну-две нервные клетки ?

Комментарии (0)