feat(monitoring): собран дашборд «Данные» из шести панелей
- Зачем:
- главный дашборд ADR 0002 отвечает на вопрос «с потоком всё в порядке?»;
три его вопроса — свежесть, брак и сходимость — у предшественника не
заданы вовсе, и в этом весь смысл экрана.
- Что:
- добавлен infra/grafana/provisioning/dashboards/data.json: свежесть, поток,
брак, сходимость, отставание чтения из Kafka и дневной слепок заказов;
порогов и цветовых вердиктов на панелях нет.
- в monitoring.md записано устройство дашборда: почему слои у панелей разные
и как считается сходимость — с доводом и двумя отвергнутыми способами.
- make config-test разбирает JSON дашбордов тем же доводом, что и файлы
дагов: сломанный файл Grafana молча оставляет прежнюю версию.
- Проверка:
- make config-test; на живом стенде make smoke 21/21 и make check-clickhouse
10/10; все шесть панелей отвечают через /api/ds/query.
This commit is contained in:
1 parent
2ccb8ff72c
commit
180ef5b94f
4 files changed
+339
-12
No files matched your search
@@ -1,16 +1,17 @@
|
|||||||
# Мониторинг: из чего собран и что скребёт
|
# Мониторинг: из чего собран и что скребёт
|
||||||
|
|
||||||
Документ описывает зону мониторинга: какие сервисы в неё входят, что собирает
|
Документ описывает зону мониторинга: какие сервисы в неё входят, что собирает
|
||||||
Prometheus, какие источники данных подготовлены в Grafana и каким
|
Prometheus, какие источники данных подготовлены в Grafana, каким пользователем
|
||||||
пользователем она ходит в ClickHouse. Зачем зоне именно такой состав и какие
|
она ходит в ClickHouse и что показывают собранные дашборды. Зачем зоне именно
|
||||||
дашборды на нём задуманы — [ADR 0002](../adr/0002-monitoring-scope.md);
|
такой состав и какие дашборды на нём задуманы — [ADR
|
||||||
устройство доступа — [ADR 0007](../adr/0007-clickhouse-access.md).
|
0002](../adr/0002-monitoring-scope.md); устройство доступа — [ADR
|
||||||
|
0007](../adr/0007-clickhouse-access.md).
|
||||||
|
|
||||||
**Что здесь есть и чего ещё нет.** Собран пол: свой образ Grafana с плагином
|
**Что здесь есть и чего ещё нет.** Собран пол: свой образ Grafana с плагином
|
||||||
ClickHouse, два подготовленных источника данных, экспортёр метрик Kafka и цель
|
ClickHouse, два подготовленных источника данных, экспортёр метрик Kafka и цель
|
||||||
для него у Prometheus. Дашбордов пока ни одного — каталог под них заведён
|
для него у Prometheus. Поверх пола стоит главный дашборд — «Данные»; «Кластер»
|
||||||
пустым, панели приносят отдельные тикеты. Правил оповещения и серверного
|
и «Запросы» приносят отдельные тикеты. Правил оповещения и серверного журнала
|
||||||
журнала `system.text_log` тоже пока нет.
|
`system.text_log` тоже пока нет.
|
||||||
|
|
||||||
## Состав
|
## Состав
|
||||||
|
|
||||||
@@ -61,9 +62,96 @@ Grafana ходит пользователем `grafana` с ролью `monitorin
|
|||||||
|
|
||||||
Каталог `infra/grafana/provisioning/dashboards/` смонтирован в контейнер и
|
Каталог `infra/grafana/provisioning/dashboards/` смонтирован в контейнер и
|
||||||
объявлен провайдером в `provider.yml`: Grafana подхватывает оттуда файлы
|
объявлен провайдером в `provider.yml`: Grafana подхватывает оттуда файлы
|
||||||
дашбордов, удалять и править их через интерфейс запрещено. Сейчас в каталоге
|
дашбордов, удалять и править их через интерфейс запрещено. Новый дашборд — это
|
||||||
только сам файл провайдера. Тикет с панелями кладёт туда JSON и в
|
новый файл в каталоге и больше ничего: в `compose.yaml` заходить не нужно, ради
|
||||||
`compose.yaml` уже не заходит — ради этого каталог и заведён заранее.
|
этого каталог и заведён заранее.
|
||||||
|
|
||||||
|
Сломанный JSON Grafana не выносит сообщением: она пишет строку в свой журнал и
|
||||||
|
оставляет прежнюю версию дашборда, то есть правка молча не появляется. Поэтому
|
||||||
|
файлы каталога разбирает `make config-test` — тем же доводом, что и файлы дагов
|
||||||
|
([testing.md](testing.md)).
|
||||||
|
|
||||||
|
## Дашборд «Данные»
|
||||||
|
|
||||||
|
`infra/grafana/provisioning/dashboards/data.json` — шесть панелей на один
|
||||||
|
экран. Вопрос у экрана один: с потоком всё в порядке? Состав панелей и довод за
|
||||||
|
каждую — [ADR 0002](../adr/0002-monitoring-scope.md).
|
||||||
|
|
||||||
|
Порогов и цветовых вердиктов на панелях нет: на дашборд смотрят глазами,
|
||||||
|
красное и зелёное живёт в проверках и дагах DQ.
|
||||||
|
|
||||||
|
| Панель | Откуда берёт | О чём отвечает |
|
||||||
|
|---|---|---|
|
||||||
|
| Свежесть | `ods.event_dist`, `ods.order_dist` | сколько прошло с последней доехавшей строки |
|
||||||
|
| Поток | `stg.hits_raw_dist`, `stg.orders_raw_dist` | сколько строк приезжает в слой сырья |
|
||||||
|
| Брак | те же и `ods.*_errors_dist` | сколько приехавшего не разобралось и какая это доля |
|
||||||
|
| Сходимость | экспортёр Kafka и `stg.hits_raw_dist` | произведённое против приземлившегося |
|
||||||
|
| Отставание | `kafka_consumergroup_lag` | сколько сообщений не прочитано |
|
||||||
|
| Дневной слепок | `stg.orders_raw_dist` | какой слепок доехал и когда |
|
||||||
|
|
||||||
|
### Почему слои у панелей разные
|
||||||
|
|
||||||
|
Свежесть спрашивают у ODS. Это слой, который читают, и у заказов дорога до него
|
||||||
|
длиннее: сырьё привозит забор, а типизированную строку кладёт разбор. Встань
|
||||||
|
разбор — свежесть ODS это покажет, свежесть сырья нет.
|
||||||
|
|
||||||
|
Поток и брак считаются по слою сырья. Сырьё записывает каждую доставку и ничего
|
||||||
|
не схлопывает, а `ReplacingMergeTree` в ODS схлопнет и повтор доставки события,
|
||||||
|
и все версии одного заказа. Считай мы поток по ODS — он занизился бы, и тем
|
||||||
|
сильнее, чем дольше живёт стенд.
|
||||||
|
|
||||||
|
Доля брака берётся от приехавшего, а не от суммы «годные плюс брак»: брак не
|
||||||
|
отдельная куча, а часть сырья, которая не прошла разбор.
|
||||||
|
|
||||||
|
Окно у панели брака — окно дашборда, и это не косметика. У `ods.*_errors_dist`
|
||||||
|
срок жизни месяц, а у `ods.event_dist` его нет вовсе: доля за всё время
|
||||||
|
сравнивала бы разные сроки хранения.
|
||||||
|
|
||||||
|
Отдельного ряда «против вчерашнего» на панели потока нет. Модельные сутки при
|
||||||
|
ускорении ×60 укладываются примерно в двадцать четыре реальные минуты, так что
|
||||||
|
соседние дни мира и без того помещаются в одно окно панели, — а реальное «вчера»
|
||||||
|
на стенде чаще всего приходится на время, когда стенда не было.
|
||||||
|
|
||||||
|
### Чтецов Kafka панели не читают
|
||||||
|
|
||||||
|
`stg.hits_raw_kafka` и `stg.orders_raw_kafka` — таблицы движка Kafka, а не
|
||||||
|
хранилище. У второго стоит `kafka_commit_on_select = 1`: запрос из панели забрал
|
||||||
|
бы слепок себе и сдвинул офсеты, а даг приёма приехал бы к пустому топику.
|
||||||
|
Приземлившееся панели считают по `stg.*_raw_dist`, произведённое — по метрикам
|
||||||
|
экспортёра.
|
||||||
|
|
||||||
|
### Как считается сходимость
|
||||||
|
|
||||||
|
Произведённое живёт в Kafka, приземлившееся — в ClickHouse, и со своей стороны
|
||||||
|
ни одна из них не видит другую. Поэтому панель смешанная: ряд «произведено в
|
||||||
|
Kafka» приходит из Prometheus, ряд «приземлилось в ClickHouse» — из ClickHouse.
|
||||||
|
Единицы общие — сообщений за интервал панели. Со стороны Kafka это разность
|
||||||
|
офсетов топика, «сейчас минус интервал назад», с суммой по обеим партициям; со
|
||||||
|
стороны ClickHouse — `count()` строк сырья по тому же интервалу.
|
||||||
|
|
||||||
|
Два соседних способа отвергнуты, и оба по замеру на живом стенде. `increase`
|
||||||
|
растягивает приращение на всё окно и на минутном интервале промахивается на
|
||||||
|
проценты: здоровый стенд выглядел бы расходящимся. Накопленный итог от начала
|
||||||
|
окна точнее их обоих, но отсчитывать его надо от края окна — а на свежем стенде
|
||||||
|
окно начинается раньше, чем появился экспортёр, и ряд пропадает целиком.
|
||||||
|
|
||||||
|
Столбцы ClickHouse подписаны правым краем, а не левым. Точка Prometheus — это
|
||||||
|
прирост к моменту времени, а `count()` по интервалу — сумма, которая набирается
|
||||||
|
к его концу; подпиши мы её началом, здоровые ряды разъехались бы ровно на один
|
||||||
|
столбец. Шаг панели при этом не мельче минуты: экспортёр опрашивают раз в
|
||||||
|
пятнадцать секунд, и на более мелком шаге ряд Kafka пошёл бы ступеньками там,
|
||||||
|
где ряд ClickHouse идёт гладко.
|
||||||
|
|
||||||
|
Точка в точку ряды всё равно не совпадут, и ждать этого не надо: опрос
|
||||||
|
экспортёра не попадает на границы столбцов, поэтому на разгоне потока линия
|
||||||
|
Kafka идёт чуть выше. Читается устойчивый разрыв между линиями, а не разница в
|
||||||
|
один столбец.
|
||||||
|
|
||||||
|
Панель смотрит на топик событий. У него приём непрерывный, и разрыв виден
|
||||||
|
сразу. Слепок заказов уезжает пачкой раз в модельные сутки, и вопрос к нему
|
||||||
|
другой — не «сходится ли поток», а «доехала ли выгрузка и когда». На него
|
||||||
|
отвечает своя панель, и модельный день она берёт из самого сообщения: в сырьё
|
||||||
|
он приезжает полем `snapshot_date`.
|
||||||
|
|
||||||
## Что проверено
|
## Что проверено
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
|
|
||||||
| Цель | Откуда | Что утверждает | Стенд | Цена |
|
| Цель | Откуда | Что утверждает | Стенд | Цена |
|
||||||
|---|---|---|---|---|
|
|---|---|---|---|---|
|
||||||
| `make config-test` | корень | Compose разбирается, файлы DAG синтаксически целы, в diff нет пробельных ошибок. О работоспособности не говорит ничего | не нужен | 1 с |
|
| `make config-test` | корень | Compose разбирается, файлы DAG и дашборды Grafana синтаксически целы, в diff нет пробельных ошибок. О работоспособности не говорит ничего | не нужен | 1 с |
|
||||||
| `make lint` | корень | Код стенда — `dags/` и `infra/superset/` — отформатирован и проходит ruff | не нужен | 0,4 с |
|
| `make lint` | корень | Код стенда — `dags/` и `infra/superset/` — отформатирован и проходит ruff | не нужен | 0,4 с |
|
||||||
| `make smoke` | корень | Стенд **собран**: службы живы, порты отвечают, подключения настроены друг на друга. Вширь и по касательной к каждой службе. Единственная цель, которая здесь правда смоук | нужен | 17 с |
|
| `make smoke` | корень | Стенд **собран**: службы живы, порты отвечают, подключения настроены друг на друга. Вширь и по касательной к каждой службе. Единственная цель, которая здесь правда смоук | нужен | 17 с |
|
||||||
| `make check-clickhouse` | корень | Всё, что спрашивают **у ClickHouse** и он отвечает сам: макросы, шарды, реплики, путь в keeper, ключ шардирования, очередь распределённых DDL, ответ словаря товаров, счёт событий стартового мира против описи | нужен | 24 с |
|
| `make check-clickhouse` | корень | Всё, что спрашивают **у ClickHouse** и он отвечает сам: макросы, шарды, реплики, путь в keeper, ключ шардирования, очередь распределённых DDL, ответ словаря товаров, счёт событий стартового мира против описи | нужен | 24 с |
|
||||||
|
|||||||
@@ -0,0 +1,236 @@
|
|||||||
|
{
|
||||||
|
"uid": "data",
|
||||||
|
"title": "Данные",
|
||||||
|
"timezone": "utc",
|
||||||
|
"schemaVersion": 42,
|
||||||
|
"refresh": "1m",
|
||||||
|
"time": {
|
||||||
|
"from": "now-1h",
|
||||||
|
"to": "now"
|
||||||
|
},
|
||||||
|
"panels": [
|
||||||
|
{
|
||||||
|
"type": "stat",
|
||||||
|
"title": "Свежесть: сколько прошло с последней доехавшей строки",
|
||||||
|
"datasource": {
|
||||||
|
"type": "grafana-clickhouse-datasource",
|
||||||
|
"uid": "clickhouse"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 5,
|
||||||
|
"w": 6,
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"colorMode": "none",
|
||||||
|
"graphMode": "none"
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"refId": "A",
|
||||||
|
"datasource": {
|
||||||
|
"type": "grafana-clickhouse-datasource",
|
||||||
|
"uid": "clickhouse"
|
||||||
|
},
|
||||||
|
"editorType": "sql",
|
||||||
|
"queryType": "table",
|
||||||
|
"format": 1,
|
||||||
|
"rawSql": "SELECT dateDiff('second', max(_load_ts), now()) AS \"события\"\nFROM ods.event_dist"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"refId": "B",
|
||||||
|
"datasource": {
|
||||||
|
"type": "grafana-clickhouse-datasource",
|
||||||
|
"uid": "clickhouse"
|
||||||
|
},
|
||||||
|
"editorType": "sql",
|
||||||
|
"queryType": "table",
|
||||||
|
"format": 1,
|
||||||
|
"rawSql": "SELECT dateDiff('second', max(_load_ts), now()) AS \"заказы\"\nFROM ods.order_dist"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "s"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "table",
|
||||||
|
"title": "Брак: сколько приехавшего не разобралось",
|
||||||
|
"datasource": {
|
||||||
|
"type": "grafana-clickhouse-datasource",
|
||||||
|
"uid": "clickhouse"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 5,
|
||||||
|
"w": 9,
|
||||||
|
"x": 6,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"options": {},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"refId": "A",
|
||||||
|
"datasource": {
|
||||||
|
"type": "grafana-clickhouse-datasource",
|
||||||
|
"uid": "clickhouse"
|
||||||
|
},
|
||||||
|
"editorType": "sql",
|
||||||
|
"queryType": "table",
|
||||||
|
"format": 1,
|
||||||
|
"rawSql": "SELECT\n source AS \"Источник\",\n countIf(NOT is_error) AS \"Приехало\",\n countIf(is_error) AS \"В брак\",\n 100 * countIf(is_error) / countIf(NOT is_error) AS \"Доля брака, %\"\nFROM\n(\n SELECT 'события' AS source, 0 AS is_error, _load_ts FROM stg.hits_raw_dist\n UNION ALL\n SELECT 'события', 1, _load_ts FROM ods.event_errors_dist\n UNION ALL\n SELECT 'заказы', 0, _load_ts FROM stg.orders_raw_dist\n UNION ALL\n SELECT 'заказы', 1, _load_ts FROM ods.order_errors_dist\n)\nWHERE $__timeFilter(_load_ts)\nGROUP BY source\nORDER BY source"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "table",
|
||||||
|
"title": "Дневной слепок заказов: какой доехал и когда",
|
||||||
|
"datasource": {
|
||||||
|
"type": "grafana-clickhouse-datasource",
|
||||||
|
"uid": "clickhouse"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 5,
|
||||||
|
"w": 9,
|
||||||
|
"x": 15,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"options": {},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"refId": "A",
|
||||||
|
"datasource": {
|
||||||
|
"type": "grafana-clickhouse-datasource",
|
||||||
|
"uid": "clickhouse"
|
||||||
|
},
|
||||||
|
"editorType": "sql",
|
||||||
|
"queryType": "table",
|
||||||
|
"format": 1,
|
||||||
|
"rawSql": "SELECT\n JSONExtractString(raw, 'snapshot_date') AS \"Слепок\",\n max(_load_ts) AS \"Доехал\",\n count() AS \"Строк\"\nFROM stg.orders_raw_dist\nGROUP BY \"Слепок\"\nORDER BY \"Слепок\" DESC\nLIMIT 10"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "timeseries",
|
||||||
|
"title": "Поток: сколько строк приезжает в слой сырья",
|
||||||
|
"datasource": {
|
||||||
|
"type": "grafana-clickhouse-datasource",
|
||||||
|
"uid": "clickhouse"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 9,
|
||||||
|
"w": 12,
|
||||||
|
"x": 0,
|
||||||
|
"y": 5
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"refId": "A",
|
||||||
|
"datasource": {
|
||||||
|
"type": "grafana-clickhouse-datasource",
|
||||||
|
"uid": "clickhouse"
|
||||||
|
},
|
||||||
|
"editorType": "sql",
|
||||||
|
"queryType": "timeseries",
|
||||||
|
"format": 0,
|
||||||
|
"rawSql": "SELECT\n $__timeInterval(_load_ts) AS t,\n count() AS \"события\"\nFROM stg.hits_raw_dist\nWHERE $__timeFilter(_load_ts)\nGROUP BY t\nORDER BY t"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"refId": "B",
|
||||||
|
"datasource": {
|
||||||
|
"type": "grafana-clickhouse-datasource",
|
||||||
|
"uid": "clickhouse"
|
||||||
|
},
|
||||||
|
"editorType": "sql",
|
||||||
|
"queryType": "timeseries",
|
||||||
|
"format": 0,
|
||||||
|
"rawSql": "SELECT\n $__timeInterval(_load_ts) AS t,\n count() AS \"заказы\"\nFROM stg.orders_raw_dist\nWHERE $__timeFilter(_load_ts)\nGROUP BY t\nORDER BY t"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "timeseries",
|
||||||
|
"title": "Сходимость: произведено в Kafka против приземлившегося в ClickHouse",
|
||||||
|
"datasource": {
|
||||||
|
"type": "datasource",
|
||||||
|
"uid": "-- Mixed --"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 9,
|
||||||
|
"w": 12,
|
||||||
|
"x": 12,
|
||||||
|
"y": 5
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"refId": "A",
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "prometheus"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "sum(kafka_topic_partition_current_offset{topic=\"hits\"}) - sum(kafka_topic_partition_current_offset{topic=\"hits\"} offset $__interval)",
|
||||||
|
"legendFormat": "произведено в Kafka",
|
||||||
|
"range": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"refId": "B",
|
||||||
|
"datasource": {
|
||||||
|
"type": "grafana-clickhouse-datasource",
|
||||||
|
"uid": "clickhouse"
|
||||||
|
},
|
||||||
|
"editorType": "sql",
|
||||||
|
"queryType": "timeseries",
|
||||||
|
"format": 0,
|
||||||
|
"rawSql": "SELECT\n -- Столбец подписан правым краем: ряд Kafka в этой точке — прирост офсета\n -- за тот же интервал, а он набирается к его концу.\n $__timeInterval(_load_ts) + INTERVAL $__interval_s SECOND AS t,\n count() AS \"приземлилось в ClickHouse\"\nFROM stg.hits_raw_dist\nWHERE $__timeFilter(_load_ts)\nGROUP BY t\nORDER BY t"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"interval": "1m"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "timeseries",
|
||||||
|
"title": "Отставание чтения из Kafka: непрочитанных сообщений",
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "prometheus"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 7,
|
||||||
|
"w": 24,
|
||||||
|
"x": 0,
|
||||||
|
"y": 14
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"refId": "A",
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "prometheus"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "sum by (consumergroup) (kafka_consumergroup_lag)",
|
||||||
|
"legendFormat": "{{consumergroup}}",
|
||||||
|
"range": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -70,6 +70,9 @@ jq -e '
|
|||||||
# обратная связь. Скрипты стенда проверять так незачем: их запускают с этой же
|
# обратная связь. Скрипты стенда проверять так незачем: их запускают с этой же
|
||||||
# машины, и ошибка вылезает при первом же запуске с номером строки.
|
# машины, и ошибка вылезает при первом же запуске с номером строки.
|
||||||
PYTHONPYCACHEPREFIX="$CACHE_DIR" uv run --no-project python -m compileall -q "$ROOT_DIR/dags"
|
PYTHONPYCACHEPREFIX="$CACHE_DIR" uv run --no-project python -m compileall -q "$ROOT_DIR/dags"
|
||||||
|
# Дашборды Grafana — тот же случай и тот же довод: сломанный JSON не сообщает о
|
||||||
|
# себе, а молча оставляет в интерфейсе прежнюю версию дашборда.
|
||||||
|
jq -e . "$ROOT_DIR"/infra/grafana/provisioning/dashboards/*.json >/dev/null
|
||||||
git -C "$ROOT_DIR" diff --check
|
git -C "$ROOT_DIR" diff --check
|
||||||
|
|
||||||
printf 'ЗЕЛЁНО: Compose, Python и пробельные ошибки diff проверены.\n'
|
printf 'ЗЕЛЁНО: Compose, Python, дашборды и пробельные ошибки diff проверены.\n'
|
||||||
Reference in new issue
Block a user