«Почему мне не приходит рассылка?» - дашборд для SSRS и PBIRS
Сервер корпоративной отчётности - штука незаметная ровно до того момента, пока кто-нибудь из бизнеса не напишет: «а почему мне со вчерашнего дня не приходит утренняя рассылка». И вот ты сидишь, открываешь портал, видишь, что подписка вроде есть, вроде активна, а письма не уходят. Лезешь в логи. А логов-то нормальных и нет - есть таблица где-то внутри базы, в которую никто никогда не заглядывал.
Знакомо? Если вы держите SSRS или его старшего брата Power BI Report Server, наверняка знакомо.
Я полез искать готовый дашборд. По уму - чтобы взять, накатить, и видеть сразу: какие рассылки падают, какие отчёты открываются по минуте, что вообще творится на сервере ночью, когда пользователей нет. Знаете, что я нашёл? Десяток одинаковых сборок «SQL Server overview» с CPU, памятью и числом сессий. Безликие счётчики, которые к отчётному серверу имеют отношение постольку-поскольку. Ни одного, который бы лез внутрь ReportServer и показывал то, ради чего этот сервер вообще существует - отчёты и их доставку.
Пришлось собирать самому. Заняло это, прямо скажем, прилично времени. Но получилось то, чем не стыдно пользоваться каждый день - и вот об этом расскажу подробно, с запросами, граблями и парой неочевидных вещей, на которые я убил несколько часов.
Коротко
- Готового вменяемого дашборда под SSRS/PBIRS в сети нет - только обобщённые «обзоры SQL Server», которые не показывают ни сбоев подписок, ни тяжёлых отчётов, ни фоновой активности pbix.
- Всё ценное лежит внутри базы
ReportServer: таблицыExecutionLog,Subscriptions,SubscriptionHistory,Catalog. Microsoft их официально не документирует, так что приходится разбираться по факту. - Power BI отчёты (
.pbix) ведут себя в логе совсем не так, как обычные пагинированные (.rdl) - и если этого не знать, метрики врут. - Мониторинг строится на двух источниках: прямые SQL-запросы к
ReportServer(бизнес-метрики) иwindows_exporterчерез VictoriaMetrics (инфраструктура нод). - По дороге всплыла классика - расхождение времени между Grafana и MSSQL на величину часового пояса. Лечится тонким слоем view поверх базы.
- PromQL-запросы к VictoriaMetrics один в один работают и в Prometheus - стек взаимозаменяем.
А кто дочитает до конца - заберёт всю сборку целиком: оба дашборда в JSON, скрипт создания view и рабочий конфиг экспортёра. Накатывается за полчаса, дальше только подставить свои источники. Так что внизу ждёт приз, не пролистывайте до него совсем уж по диагонали.
Почему вообще понадобился отдельный дашборд
Reporting Services живёт с нами очень давно. И за эти годы у него так и не появилось внятной системы наблюдения. Парадокс: система зрелая, стоит в каждом втором энтерпрайзе, генерит критичную для бизнеса отчётность - а смотреть на её здоровье нечем.
С приходом Power BI Report Server стало только хуже. Теперь на одном сервере соседствуют два мира: классические пагинированные отчёты .rdl и интерактивные Power BI отчёты .pbix. Ведут они себя по-разному, нагружают сервер по-разному, и пишут в журнал тоже по-разному. А мониторинг, если он вообще есть, обычно покрывает в лучшем случае «жив ли сервис».
Мне же нужно было видеть конкретные вещи. Не абстрактную загрузку CPU, а ответы на рабочие вопросы:
- Какие отчёты упали при открытии и с какой ошибкой - чтобы отдать трейс разработчику отчёта, а не разводить руками.
- Какие подписки и рассылки не доставлены - и кому они принадлежат, чтобы было с кого спросить.
- Какие отчёты тяжёлые - где сервер реально потеет, выгребая данные из источника.
- Чем сервер занят ночью, когда живых пользователей нет, а нагрузка почему-то есть.
Ни один готовый дашборд из тех, что я видел, на эти вопросы не отвечает. Потому что ответы лежат внутри самой базы ReportServer, а не в системных счётчиках Windows. Туда и пришлось копать.
Что внутри базы ReportServer
Сразу важная оговорка: Microsoft не документирует схему ReportServer. Официально. Это внутренняя база Reporting Services, и её структуру могут менять между версиями без предупреждения. Никакого справочника полей, как для системных представлений SQL Server, по ней не существует и не появится.
Поэтому всё, что ниже - результат раскопок по факту, на живой базе, со сверкой через sys.columns и неофициальные источники вроде mssqltips. Проверяйте на своей версии, поля могут отличаться.
Ключевые таблицы, ради которых всё затевалось:
ExecutionLogStorage - журнал выполнения. Каждый запуск отчёта, каждое открытие, каждая отправка подписки оставляет здесь строку. Поверх неё есть представления ExecutionLog, ExecutionLog2, ExecutionLog3 - они декодируют числовые коды в человекочитаемый вид. Я работал через ExecutionLog3. Главные поля:
ItemPath- путь к отчёту.UserName- кто запускал.RequestType- тип запроса:Interactive(пользователь открыл),Subscription(рассылка),Refresh Cache(фоновое обновление данных).Format- формат вывода:RPL(просмотр на экране),PDF,EXCELOPENXML,MHTMLи так далее.ItemAction- действие:Render,ConceptualSchema,DataRefresh,SaveToCatalogи прочие.TimeStart,TimeEnd- начало и конец.TimeDataRetrieval,TimeProcessing,TimeRendering- три фазы выполнения в миллисекундах. Вот это золото для поиска тяжёлых отчётов.Status- результат.rsSuccessили код ошибки.ByteCount,RowCount- объём результата и число строк.
Subscriptions - сами подписки. Кто владелец, какое расписание, какое описание, когда последний запуск.
SubscriptionHistory - история отработки подписок. Вот тут ключевой момент: ошибки доставки рассылок живут именно здесь, а не в ExecutionLog. Об этом дальше отдельно.
Catalog - все объекты сервера: отчёты, папки, источники данных. Тип объекта в поле Type: 2 - пагинированный отчёт (.rdl), 13 - Power BI отчёт (.pbix), 1 - папка, и так далее.
Users - пользователи. Сюда джойнятся все эти OwnerID, CreatedByID, ModifiedByID, чтобы вместо GUID показать живое имя.
Самое коварное: pbix - это не rdl
Вот на этом месте я потратил больше всего времени, и вот это нигде толком не описано.
Когда вы открываете обычный пагинированный отчёт .rdl, сервер честно его рендерит и записывает в лог реальные фазы: столько-то на выборку данных, столько-то на обработку, столько-то на рендеринг. Красиво, измеримо, понятно.
С Power BI отчётом всё иначе. Рендеринг .pbix происходит в браузере у пользователя, а не на сервере. Поэтому фазы времени в логе по нему - нули. Сервер только отдаёт модель данных и обслуживает запросы визуалов, а рисует всё клиент.
И вот что это означает на практике. Одно открытие .pbix-отчёта порождает в логе не одну строку, а целую пачку. Один заход пользователя - это:
- одна строка
ConceptualSchema(собственно факт открытия отчёта), - и следом десятки строк
QueryData- по запросу на каждый визуал на странице.
Если вы наивно посчитаете «вызовы отчёта» через COUNT(*), у вас один любопытный аналитик, потыкавший в дашборд, превратится в сотню «вызовов». Я это увидел на реальных данных: 70 строк в логе за пять минут схлопывались в 8 настоящих взаимодействий. А на большем срезе - 354 сырых строки на 19 реальных открытий.
Как считать правильно? Открытие .pbix-отчёта - это ровно одна строка с ItemAction = 'ConceptualSchema'. Не QueryData, не ASModelStream - только ConceptualSchema. Вот надёжный маркер:
SELECT COUNT(*) AS pbix_opens
FROM ReportServer.dbo.ExecutionLog3
WHERE ItemAction = 'ConceptualSchema'
AND TimeStart >= @from AND TimeStart < @to;
Одна запись - одно открытие. Проверено на живых данных: совпадает с реальным числом заходов, без шума визуалов.
И раз уж зашла речь про фазы - для панели «тяжёлые отчёты», которая считает среднее время по TimeDataRetrieval + TimeProcessing + TimeRendering, нужно .pbix исключать вообще. Иначе их нули занижают средние, и реально медленные пагинированные отчёты тонут в этой массе. Фильтр простой: Format NOT IN ('PBIX', 'DataModel') AND Format IS NOT NULL.
Подписки: ошибки доставки лежат в отдельной таблице
Ещё одна неочевидная вещь. Казалось бы, упала рассылка - смотри в ExecutionLog. Так вот, там их и не будет.
ExecutionLog со строкой RequestType = 'Subscription' фиксирует, что отчёт под рассылку сгенерировался. А вот доставлен ли он - ушло ли письмо, записался ли файл на сетевую шару - это уже в SubscriptionHistory. И это разные события. Отчёт может прекрасно отрендериться (в ExecutionLog будет rsSuccess), а письмо не уйдёт, потому что почтовый сервер прилёг. В ExecutionLog тишина, а в SubscriptionHistory - ошибка.
Поэтому «упавшие подписки» надо считать из SubscriptionHistory, где Status > 0. Вот так выглядит сводка по проблемным рассылкам - с владельцем, описанием и числом ошибок за период:
SELECT
s.SubscriptionID,
c.Path AS ReportPath,
u.UserName AS Owner,
s.Description,
s.LastStatus,
s.LastRunTime,
she.ErrorsInPeriod
FROM ReportServer.dbo.Subscriptions s
JOIN ReportServer.dbo.[Catalog] c ON c.ItemID = s.Report_OID
LEFT JOIN ReportServer.dbo.Users u ON u.UserID = s.OwnerID
INNER JOIN (
SELECT
sh.SubscriptionID,
SUM(CASE WHEN sh.Status > 0 THEN 1 ELSE 0 END) AS ErrorsInPeriod
FROM ReportServer.dbo.SubscriptionHistory sh
GROUP BY sh.SubscriptionID
) she ON she.SubscriptionID = s.SubscriptionID
WHERE she.ErrorsInPeriod > 0
ORDER BY s.LastRunTime DESC;
Маленькая деталь, на которой легко обжечься: джойн к Users обязательно LEFT. Если у подписки удалили владельца (уволился человек, вычистили учётку из AD), а сама подписка осталась - при INNER JOIN она просто пропадёт из выборки. А это как раз самые интересные подписки, осиротевшие. Их надо видеть, а не прятать.
И ещё. В таблице Subscriptions живут не только рассылки. Запланированное обновление данных .pbix (тот самый scheduled refresh) реализовано через тот же механизм подписок и сидит здесь же, с EventType = 'DataModelRefresh'. Если вы считаете «настоящие» рассылки - эти записи надо отфильтровать: EventType <> 'DataModelRefresh'. Иначе refresh-задачи раздувают вам статистику подписок.
Отдельно стоит сказать про трейс для разработчиков. Сводная таблица показывает, что подписка падала и сколько раз - но разработчику отчёта этого мало, ему нужен текст ошибки. Поэтому я сделал клик по строке проваливающимся во второй дашборд - «История подписки». Технически это data link на ячейке: при клике Grafana передаёт SubscriptionID и путь отчёта через URL в переменные дочернего дашборда, и тот показывает всю историю доставки именно этой подписки с расшифрованными сообщениями об ошибках.
Сообщения, кстати, лежат в поле Details записи истории - это JSON, внутри массив Errors с полем Message. Достаются они через OPENJSON с JSON_VALUE, кириллица декодируется, несколько сообщений склеиваются в одну ячейку. Получается, что инженер кликнул по упавшей рассылке - и сразу видит «не удалось подключиться к SMTP» или «источник данных недоступен», готовый текст для передачи дальше. Этот дочерний дашборд тоже прилагаю ниже.
Чем сервер занят, когда никого нет
Любимая моя панель. Открываешь утром графики, а ночью, в три часа, когда ни одного живого пользователя, - всплеск нагрузки. Откуда?
Это фоновое обновление моделей .pbix. Сервер по расписанию подтягивает свежие данные в импортированные модели, чтобы утром пользователь видел актуальные цифры. В логе это RequestType = 'Refresh Cache' под системной учёткой, и цикл из трёх стадий: DataRefresh (тянет данные из источника), ASModelStream (загоняет модель в движок) и SaveToCatalog (пишет обновлённую модель обратно в базу).
Вот на SaveToCatalog есть ByteCount - размер записанной модели. По нему можно построить график «объём записи моделей при обновлении» и наглядно видеть, когда и насколько сервер сам себя нагружает:
SELECT
$__timeGroup(TimeStartUtc, '10m', 0) AS time,
SUM(CAST(ByteCount AS BIGINT)) AS [Объём записи моделей]
FROM ReportServer.dbo.ExecutionLog3_utc
WHERE RequestType = 'Refresh Cache'
AND Status = 'rsSuccess'
AND ItemAction = 'SaveToCatalog'
AND $__timeFilter(TimeStartUtc)
GROUP BY $__timeGroup(TimeStartUtc, '10m', 0)
ORDER BY 1;
Важно понимать, что именно показывают эти байты. Это размер уже сжатой модели, которую сервер записал обратно в каталог - в саму базу ReportServer. То есть вес финального артефакта, а не объём сырых данных, вычитанных из источника (он обычно в разы больше - VertiPaq хорошо жмёт). Практический вывод простой: тяжёлые .pbix реально раздувают вам базу и бэкапы. У меня попадались модели под полтора гигабайта на одно обновление. Полезно знать в лицо такие отчёты.
Кстати, заметьте имя view в запросе - ExecutionLog3_utc. Не ExecutionLog3. Вот об этом - отдельная и поучительная история.
История про три часа, которые сломали половину дашборда
Когда основные панели заработали, я наткнулся на странность. Беру пик активности на графике - вижу аномальные 172 события в одну точку. Иду в SSMS проверить - а там по этому времени ноль. Пусто. Запрос тот же, данные те же, цифры разные.
Это классика связки Grafana плюс SQL, и грабли эти ловят многих.
Дело в том, что Grafana ожидает время в колонке всегда в UTC. А Reporting Services пишет TimeStart в локальном времени сервера. У меня это московское, плюс три часа от UTC. И когда Grafana формирует фильтр по времени, она шлёт в запрос границы в UTC, а сравнивает их с колонкой, которая в локальном времени. Сдвиг ровно на три часа. «Пик в 21:10» на графике физически выбирал данные за 18:10 - то есть ночной пик рассылок, где этих событий и правда под две сотни.
Самое противное: настройкой таймзоны дашборда это не лечится. Потому что источников два. VictoriaMetrics отдаёт время в UTC, как и положено. А MSSQL - в локальном. Любая глобальная настройка зоны починит один источник и сломает второй. Поставишь UTC - поедут графики windows_exporter. Поставишь местное - поедут SQL-панели.
Правильное решение - привести оба источника к UTC. VictoriaMetrics уже в UTC, значит надо подтянуть MSSQL. И вот тут красивый ход: не трогать ни системные таблицы (это запрещено и небезопасно), ни каждый запрос по отдельности, а сделать тонкий слой view, который добавляет к данным колонку с пересчитанным временем.
CREATE OR ALTER VIEW dbo.ExecutionLog3_utc AS
SELECT *,
DATEADD(HOUR, DATEDIFF(HOUR, GETDATE(), GETUTCDATE()), TimeStart) AS TimeStartUtc
FROM dbo.ExecutionLog3;
DATEDIFF(HOUR, GETDATE(), GETUTCDATE()) вычисляет смещение сервера от UTC прямо на лету, без хардкода тройки. Сервер сам себя спрашивает «на сколько я отличаюсь от UTC» и сдвигает. Перевели на зимнее время, переехали в другой регион - формула продолжит работать.
Таких view получилось три: для ExecutionLog3, для SubscriptionHistory (там колонка StartTime) и для Subscriptions (там LastRunTime). Дальше все панели переключаются на эти view и на UTC-колонки - и в фильтрах времени, и в выводимых датах. После этого оба источника живут в одной зоне, таймзона дашборда ставится в браузерную, и всё сходится: и графики из VictoriaMetrics, и таблицы из SQL показывают одно и то же время.
Нюанс, который стоит запомнить намертво: в SQL-панелях UTC-колонку надо использовать везде - и в фильтрах, и в выводе. Если оставить в SELECT обычную локальную колонку, Grafana при отображении прибавит к ней те же три часа, и вы получите время из будущего. Я на этом споткнулся - в таблице подписок «последний запуск» показывал время, которого ещё не наступило.
На чём всё стоит
Архитектура простая и разделённая по природе данных.
Бизнес-метрики - прямыми SQL-запросами к базе ReportServer. Всё, что про отчёты, подписки, ошибки, тяжесть - это туда. Grafana с MSSQL-датасорсом, запросы вы видели выше.
Инфраструктура нод - через windows_exporter, который отдаёт метрики в VictoriaMetrics, а Grafana их оттуда читает. Это CPU, память, диски серверов, где крутится сам отчётный сервис.
Отдельно отмечу: PromQL-запросы, которые я приведу для VictoriaMetrics, работают один в один в Prometheus. Это совместимый язык запросов, так что если у вас Prometheus вместо VictoriaMetrics - просто берите и используйте, менять ничего не надо.
windows_exporter: включаем сбор по процессам
По умолчанию windows_exporter не собирает метрики в разрезе процессов. А нам это нужно - чтобы видеть отдельно потребление самого сервиса отчётов и встроенного движка Analysis Services, который обслуживает .pbix-модели.
Включается коллектор process в конфиге экспортёра:
collectors:
enabled: "[defaults],process"
collector:
process:
include: .+
exclude: ''
После правки - перезапуск службы, и через минуту в хранилище появляются метрики windows_process_* по всем процессам ноды.
Маленькая ремарка из практики, которая сэкономит вам полчаса. Если на одной ноде сбор есть, а на другой нет - первым делом проверьте, не читает ли служба конфиг из другого файла. У меня на одном сервере экспортёр запускался с config.yml, а правил я по привычке config.yaml. Расширения разные, файл живой - другой. Правки уходили в пустоту, метрики не появлялись. Минут сорок я искал проблему не там. Проверяется одной строкой:
(Get-CimInstance win32_service | Where-Object Name -eq 'windows_exporter').PathName
В выводе будет видно, какой файл реально читает служба.
Метрики из VictoriaMetrics
Самое полезное - в разрезе процессов отчётного сервера.
Память встроенного Analysis Services. Движок msmdsrv держит в памяти модели данных .pbix. Именно он раздувается при обновлении моделей и при активной работе пользователей с Power BI отчётами. По сути - сколько оперативки съели Power BI модели:
windows_process_working_set_bytes{process="msmdsrv", instance=~"$rs_instance"}
Загрузка процессора этим же движком. Тут есть тонкость с интерпретацией. Метрика нормируется на одно ядро, поэтому значения легко переваливают за сто процентов - триста означает работу на трёх ядрах одновременно. Это нормально: обновление моделей идёт в несколько потоков.
sum by (instance) (rate(windows_process_cpu_time_total{process="msmdsrv", instance=~"$rs_instance"}[$__rate_interval])) * 100
Задержка дисков нод. Для отчётного сервера диск часто оказывается узким местом - временные файлы при рендеринге тяжёлых отчётов, кэш. Средняя задержка операции считается как отношение накопленного времени к числу операций, отдельно по чтению и записи или общая:
rate(windows_logical_disk_read_write_latency_seconds_total{instance=~"$rs_instance", volume=~"[A-Z]:"}[$__rate_interval])
/
clamp_min(
rate(windows_logical_disk_reads_total{instance=~"$rs_instance", volume=~"[A-Z]:"}[$__rate_interval])
+
rate(windows_logical_disk_writes_total{instance=~"$rs_instance", volume=~"[A-Z]:"}[$__rate_interval]),
1
)
Тут пара рабочих хитростей. Фильтр volume=~"[A-Z]:" оставляет только тома с буквами и отсекает служебные разделы вроде HarddiskVolume1, которые только шумят. А clamp_min(..., 1) страхует от деления на ноль, когда операций в интервале не было.
Здоровые значения для SSD - единицы миллисекунд. Десятки и сотни - диск перегружен и тормозит рендеринг. На виртуалках с общим хранилищем чуть выше нормы из-за слоя виртуализации, это ожидаемо.
Руководство по установке
Импорт несложный, но есть несколько шагов, которые надо выполнить именно в этом порядке, иначе панели приедут пустыми. Разберём по пунктам.
Шаг 1. Создать три view в базе ReportServer
Панели читают время не из системных представлений напрямую, а из трёх view с пересчитанной в UTC колонкой - ExecutionLog3_utc, SubscriptionHistory_utc, Subscriptions_utc. Без них SQL-панели выдадут ошибку «invalid object name». Поэтому это самый первый шаг.
Готовый скрипт - в файле create_view_report_server.sql (прилагается ниже). Выполняется один раз в базе ReportServer:
USE [ReportServer]
GO
CREATE OR ALTER VIEW dbo.ExecutionLog3_utc AS
SELECT *,
DATEADD(HOUR, DATEDIFF(HOUR, GETDATE(), GETUTCDATE()), TimeStart) AS TimeStartUtc
FROM dbo.ExecutionLog3;
GO
CREATE OR ALTER VIEW dbo.SubscriptionHistory_utc AS
SELECT *,
DATEADD(HOUR, DATEDIFF(HOUR, GETDATE(), GETUTCDATE()), StartTime) AS StartTimeUtc
FROM dbo.SubscriptionHistory;
GO
CREATE OR ALTER VIEW dbo.Subscriptions_utc AS
SELECT *,
DATEADD(HOUR, DATEDIFF(HOUR, GETDATE(), GETUTCDATE()), LastRunTime) AS LastRunTimeUtc
FROM dbo.Subscriptions;
GO
Эти view ничего не меняют в системных таблицах - они только добавляют сверху колонку с временем, пересчитанным в UTC. DATEDIFF(HOUR, GETDATE(), GETUTCDATE()) вычисляет смещение сервера от UTC на лету, без хардкода. Безопасно для последующих обновлений Reporting Services.
Шаг 2. Включить коллектор process в windows_exporter
По умолчанию windows_exporter не собирает метрики в разрезе процессов, а они нужны - чтобы видеть отдельно сам сервис отчётов и движок Analysis Services (msmdsrv), который обслуживает .pbix-модели.
В конфиге экспортёра (файл config.yml, прилагается ниже целиком) коллектор process должен быть в списке включённых, плюс блок с фильтром процессов:
collectors:
enabled: "[defaults],license,process,scheduled_task,textfile,time,vmware"
collector:
process:
include: .+
exclude: ''
После правки - перезапуск службы: Restart-Service windows_exporter. Через минуту в хранилище появятся метрики windows_process_*.
Две вещи, на которых легко потерять время. Первое: убедитесь, что служба читает именно тот файл, который вы правите - проверяется через (Get-CimInstance win32_service | Where-Object Name -eq 'windows_exporter').PathName. Расширения .yml и .yaml в одной папке - разные файлы, и правка не в тот уходит впустую. Второе: если служба не стартует после правки - чаще всего порт 9182 занят зависшим процессом экспортёра, ищется через netstat -ano | findstr ":9182".
Шаг 3. Импортировать оба дашборда
В Grafana: Dashboards → New → Import → Upload JSON. Сначала основной дашборд, затем дашборд истории подписки. При импорте Grafana запросит источники данных:
- MSSQL - подключение к базе
ReportServer. Все панели подписок, ошибок и тяжёлых отчётов читают отсюда. Если у вас именованный инстанс или база называется иначе - поправьте имя в запросах (вездеReportServer.dbo.*). - VictoriaMetrics или Prometheus - метрики
windows_exporter. Отсюда процессор, память, диски нод. Запросы совместимы с обоими хранилищами.
Дашборд истории подписки просит только MSSQL. Привяжите оба дашборда к одному и тому же MSSQL-датасорсу - иначе провал по клику из таблицы упавших подписок работать не будет.
Шаг 4. Настроить переменную с нодами
Самый важный шаг, и тут два сценария.
Несколько нод (scale-out PBIRS). Дашборд тянет список нод динамически - переменная rs_instance спрашивает у метрик, какие хосты есть:
label_values(windows_os_hostname{hostname=~"$host_filter"},instance)
$host_filter - вторая переменная, куда вписывается регулярка под имена ваших серверов. По умолчанию стоит пример srv-rs-0[12].*. Поменяйте на свой паттерн - и список инстансов подтянется сам. Новый сервер с windows_exporter попадёт в список автоматически, без правки дашборда.
Частая ошибка: если паттерн в host_filter не совпадает с реальными именами хостов, список будет пустым и инфраструктурные панели окажутся без данных. Симптом «узлы не подтягиваются» - почти всегда это. Проверьте в Explore, что windows_os_hostname{hostname=~"ваш-паттерн"} вообще что-то возвращает. И учтите: в части версий экспортёра имя хоста лежит в windows_cs_hostname, а не в windows_os_hostname - если первая пустая, пробуйте вторую.
Один инстанс SSRS. Динамический список не нужен. Откройте переменную host_filter и впишите туда точное имя своего сервера (или маску под него) - единственная нода подтянется. Лишнюю плашку сверху уберите: в настройках переменной host_filter поставьте Show on dashboard → Nothing. Вписали имя один раз - и забыли.
Шаг 5. Проверить вспомогательные переменные
Ещё две переменные работают из коробки, трогать обычно не нужно:
report- выпадающий список всех отчётов (тянется изCatalog), фильтрует панели по конкретному отчёту.__ALL__показывает всё.fail_class- скрытый переключатель классов сбоев (.rdl / открытия .pbix / refresh) для фильтрации.
После этих пяти шагов дашборд оживёт полностью: SQL-панели увидят view, инфраструктурные - метрики нод, провал в историю подписки заработает.
Готовые файлы
Забирайте всё, что нужно для развёртывания:
- Основной дашборд - pbirs-monitoring-dashboard.json - 21 панель: счётчики сбоев, таблицы подписок и отчётов с трейсом, производительность, фоновая нагрузка, здоровье нод.
- Дашборд истории подписки - pbirs-sub-history-dashboard.json - открывается по клику из таблицы упавших подписок, показывает расшифрованные ошибки доставки.
- Скрипт создания view - create_view_report_server.sql - три UTC-view, выполнить в базе
ReportServer(Шаг 1). - Конфиг windows_exporter - config.yml - рабочий конфиг с включённым коллектором
process(Шаг 2).
В дашбордах нет ни реальных хостов, ни строк подключения - datasource подставите свои при импорте.
Что в итоге получилось
Дашборд, который отвечает на рабочие вопросы вместо показа безликих графиков.
Сверху - счётчики сбоев: сколько подписок упало, сколько отчётов не открылось, сколько подписок осиротело без владельца. Дальше - таблицы с трейсом: какой отчёт, какая ошибка, кто запускал, кто последним менял. Этот трейс удобно передать разработчику отчёта - вместо «у меня не работает» он получает конкретный код ошибки и время.
Тут напрашивается следующий шаг, до которого у меня пока не дошли руки. Раз в таблице уже есть ModifiedByID - кто последним правил отчёт, - можно навесить на эти панели алертинг Grafana и при сбое автоматически дёргать того, кто за отчёт отвечает. Если отчётами занимается команда аналитиков, это закрывает разрыв «сломалось у пользователя - автор узнал через неделю»: автор получает уведомление сразу, в момент падения, с готовым трейсом. Пока это в планах, но архитектурно всё для этого на дашборде уже лежит.
Ниже - производительность: время по фазам для пагинированных отчётов, топ самых тяжёлых, разбивка нагрузки. Отдельно - фоновая активность .pbix: открытия по ConceptualSchema, обновления моделей, объём записи в каталог. И в самом низу - здоровье нод: процессор и память сервиса, задержки дисков, паспорт серверов с характеристиками.
Времени ушло прилично, чего уж там. Причём бо́льшую его часть съели не запросы, а вот эти неочевидные грабли: .pbix врёт про фазы, ошибки доставки лежат в другой таблице, время разъезжается на часовой пояс. Зато теперь, когда приходит «а почему мне не пришла рассылка», ответ находится за пятнадцать секунд. Раньше на это уходило полдня раскопок в логах.
Если соберётесь повторять - главное, держите в голове, что .pbix живёт по своим правилам, не по правилам .rdl. Это сэкономит вам тот самый день, который потратил я.
Основатель WARP.D. 30 лет в отрасли - от администрирования SQL Server до архитектуры data-платформ на открытом стеке. Банки, инвестиционные компании, федеральный ритейл.