Мониторинг SSRS и Power BI Report Server: дашборд для Grafana

К этой статье меня подтолкнула причина донельзя прозаическая. Сижу недавно на брифинге, и прилетает жалоба: сервер отчётов работает из рук вон плохо. Сервер при этом не мой, в орбиту обслуживания он не входил, а тут пришлось вспомнить, как оно всё устроено у SQL Server Reporting Services (SSRS), вспомнить молодость, так сказать. Вспомнил. Заодно собрал то, чего мне самому когда-то не хватало, — и решил, что пора всё это выложить в одну статью.
Сервер отчётов — сервис незаметный, пока кто-нибудь из бизнеса не напишет: «а почему мне со вчерашнего дня не приходит утренняя рассылка». Сидишь, открываешь портал, видишь, что подписка вроде есть, вроде активна, а письма не уходят. Ну и как оно всегда было? Лезешь в логи, а логов нормальных нет, есть только таблица где-то внутри базы, в которую редко кто, кроме DBA, и заглядывал.
Знакомо? Если вы держите SSRS или его старшего брата Power BI Report Server, наверняка знакомо.
Вопросы, на которые SSRS обязан отвечать сам, звучат просто.
Какие рассылки упали этой ночью и по какой причине?
Какие отчёты открывают чаще всего, а какие не открывали полгода? Это обычная статистика использования отчётов, которой в портале нет.
Где сервер реально упирается в ресурсы?
Кто владелец подписки, которая ломается третью неделю подряд?
Штатными средствами ни один из этих ответов не достаётся: портал показывает список объектов, а журнал выполнения лежит таблицей в служебной базе, без графиков поверх. Ниже — как я собрал дашборд в Grafana поверх базы ReportServer и какие грабли попались по дороге.
Я полез искать готовый дашборд. По уму так, чтобы взять, накатить, и видеть сразу: какие рассылки падают, какие отчёты открываются по минуте, что вообще творится на сервере ночью, когда пользователей нет. Знаете, что я нашёл? Десяток одинаковых сборок «SQL Server overview» с CPU, памятью и числом сессий. Безликие счётчики, которые к мониторингу SSRS имеют отношение постольку-поскольку. Ни одного, который бы лез внутрь ReportServer и показывал то что нужно, то есть отчёты, их нагрузку и их доставку.
Пришлось собирать самому. Заняло это, прямо скажем, прилично времени. Но получилось то, чем не стыдно пользоваться каждый день — и вот об этом расскажу подробно, с запросами, граблями и парой неочевидных вещей, на которые я убил несколько часов.
Коротко
Готового вменяемого дашборда под SSRS/PBIRS в сети нет, только обобщённые «обзоры SQL Server», которые не показывают ни сбоев подписок, ни тяжёлых отчётов, ни фоновой активности pbix.
Всё ценное лежит внутри базы
ReportServer: таблицыExecutionLog,Subscriptions,SubscriptionHistory,Catalog. Microsoft их официально не документирует, так что приходится разбираться по факту.Power BI отчёты (
.pbix) ведут себя в логе совсем не так, как обычные пагинированные (.rdl) — и если этого не знать, метрики врут.Мониторинг SSRS строится на двух источниках: прямые SQL-запросы к
ReportServer(бизнес-метрики) иwindows_exporterчерез VictoriaMetrics (инфраструктура нод).По дороге всплыла классика — расхождение времени между Grafana и MSSQL на величину часового пояса. Лечится тонким слоем view поверх базы.
PromQL-запросы к VictoriaMetrics один в один работают и в Prometheus — стек взаимозаменяем.
Всю сборку — оба дашборда в JSON, скрипт создания view и рабочий конфиг экспортёра — выложил на GitHub, ссылка в конце. Накатывается за полчаса, дальше только свои источники подставить.
Почему вообще понадобился отдельный дашборд
SQL Server Reporting Services живёт с нами очень давно, и за эти годы у него так и не появилось внятной системы наблюдения. Парадокс: система зрелая, стоит в каждом втором энтерпрайзе, генерит критичную для бизнеса отчётность, а смотреть на её здоровье нечем.
С приходом Power BI Report Server стало только хуже. Теперь на одном сервере соседствуют два мира: классические пагинированные отчёты .rdl и интерактивные Power BI отчёты .pbix. Ведут они себя по-разному, нагружают сервер по-разному, и пишут в журнал тоже по-разному. А мониторинг Reporting Services, если он вообще есть, обычно покрывает в лучшем случае «жив ли сервис».
Мне же нужно было видеть конкретные вещи. Не абстрактную загрузку CPU, а ответы на рабочие вопросы:
Какие отчёты упали при открытии и с какой ошибкой — чтобы отдать трейс разработчику отчёта, а не разводить руками.
Какие подписки и рассылки не доставлены — и кому они принадлежат, чтобы было с кого спросить.
Какие отчёты тяжёлые — где сервер реально потеет, выгребая данные из источника.
Чем сервер занят ночью, когда живых пользователей нет, а нагрузка почему-то есть.
Ни один готовый дашборд из тех, что я видел, на эти вопросы не отвечает, потому что ответы лежат внутри самой базы ReportServer, а не в системных счётчиках Windows. Копать пришлось именно туда, в служебную базу Reporting Services.
Что внутри базы ReportServer
Сразу важная оговорка: Microsoft не документирует схему ReportServer. Официально. Это внутренняя база Reporting Services, и её структуру могут менять между версиями без предупреждения. Никакого справочника полей, как для системных представлений SQL Server, по ней не существует и не появится.
Поэтому всё, что ниже, это результат раскопок по факту, на живой базе, со сверкой через sys.columns и неофициальные источники вроде mssqltips. Проверяйте на своей версии, поля могут отличаться.
Ещё одна вещь, которую стоит знать до того, как накатите дашборд. Журнал выполнения хранится не вечно: по умолчанию Reporting Services держит записи 60 дней, дальше ночное задание их вычищает. Управляется это параметром ExecutionLogDaysKept в таблице ConfigurationInfo. Ноль означает «хранить бесконечно», и вот так делать не надо: на нагруженном сервере ExecutionLogStorage пухнет быстро, особенно если в хозяйстве много .pbix с их десятками строк на одно открытие. Если нужна история за год, правильнее не раздувать срок хранения, а перекладывать данные в отдельную таблицу своим расписанием.
Ключевые таблицы, которые будем смотреть:
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» или «источник данных недоступен», готовый текст для передачи дальше. Дочерний дашборд для этого тоже лежит в сборке, отдельно настраивать ничего не нужно.
Чем PBIRS занят ночью, когда никого нет
Любимая моя панель. Открываешь утром графики, а ночью, в три часа, когда ни одного живого пользователя, случается всплеск нагрузки. Откуда?
Это фоновое обновление моделей .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 при отображении прибавит к ней те же три часа, и вы получите время из будущего. Я на этом споткнулся: в таблице подписок «последний запуск» показывал время, которого ещё не наступило. (не пугайтесь, в дашборде по ссылке на GitHub уже всё настроено)
Архитектура мониторинга: SQL-метрики и windows_exporter
Бизнес-метрики — прямыми SQL-запросами к базе ReportServer. Всё, что про отчёты, подписки, ошибки, тяжесть — это туда. Grafana с MSSQL-датасорсом, запросы вы видели выше.
Инфраструктура нод — через windows_exporter, который отдаёт метрики в VictoriaMetrics, а Grafana их оттуда читает. Это CPU, память, диски серверов, где крутится сам отчётный сервис.
Отдельно отмечу: PromQL-запросы, которые я приведу для VictoriaMetrics, работают один в один в Prometheus. Это совместимый язык запросов, так что если у вас Prometheus вместо VictoriaMetrics, просто берите и используйте, менять ничего не надо.
windows_exporter: включаем сбор по процессам
По умолчанию windows_exporter не собирает метрики в разрезе процессов. А нам это нужно, чтобы видеть отдельно потребление самого сервиса PBIRS и встроенного движка Analysis Services, который обслуживает .pbix-модели.
Включается коллектор process в конфиге экспортёра:
collectors:
enabled: "[defaults],process"
collector:
process:
include: .+
exclude: ''
После правки — перезапуск службы, и через минуту в хранилище появляются метрики windows_process_* по всем процессам ноды.
Маленькая ремарка из практики, которая сэкономит вам полчаса. Если у вас двухнодовый SSRS и на одной ноде сбор есть, а на другой нет — первым делом проверьте, не читает ли служба конфиг из другого файла. У меня на одном сервере экспортёр запускался с 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
Задержка дисков нод. Для Power BI Report Server диск часто оказывается узким местом — временные файлы при рендеринге тяжёлых отчётов, кэш. Средняя задержка операции считается как отношение накопленного времени к числу операций, отдельно по чтению и записи или общая:
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 — единицы миллисекунд. Десятки и сотни — диск перегружен и тормозит рендеринг. На виртуалках с общим хранилищем чуть выше нормы из-за слоя виртуализации, это ожидаемо.
Установка дашборда в Grafana: пять шагов
Импорт несложный, но есть несколько шагов, которые надо выполнить именно в этом порядке, иначе панели приедут пустыми.
Шаг 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 не собирает метрики в разрезе процессов, а они нужны — чтобы видеть отдельно сам сервис SSRS и движок 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. Все панели подписок, ошибок и тяжёлых отчётов читают отсюда.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, инфраструктурные — метрики нод, провал в историю подписки заработает.
Где забрать сборку
Весь комплект для развёртывания лежит в репозитории, файлами, которые можно сразу скачать:
Основной дашборд — 21 панель: счётчики сбоев, таблицы подписок и отчётов с трейсом, производительность, фоновая нагрузка, здоровье нод.
Дашборд истории подписки — открывается по клику из таблицы упавших подписок, показывает расшифрованные ошибки доставки.
Скрипт создания view — три UTC-view, выполнить в базе
ReportServer(Шаг 1).Конфиг windows_exporter — рабочий конфиг с включённым коллектором
process(Шаг 2).
Забрать всё одним пакетом: https://github.com/maxpiter/ssrs-pbirs-grafana-dashboard
В дашбордах нет ни реальных хостов, ни строк подключения — датасорсы подставите свои при импорте.
Что показывает готовый дашборд
Дашборд, который отвечает на рабочие вопросы вместо показа безликих графиков.
Сверху — счётчики сбоев: сколько подписок упало, сколько отчётов не открылось, сколько подписок осиротело без владельца. Дальше — таблицы с трейсом: какой отчёт, какая ошибка, кто запускал, кто последним менял. Этот трейс удобно передать разработчику отчёта — вместо «у меня не работает» он получает конкретный код ошибки и время. Заодно получается аудит обращений: видно, кто открывал отчёт, когда и с каким результатом.
Тут напрашивается следующий шаг, до которого у меня пока не дошли руки. Раз в таблице уже есть ModifiedByID — кто последним правил отчёт, — можно навесить на эти панели алертинг Grafana и при сбое автоматически дёргать того, кто за отчёт отвечает. Если отчётами занимается команда аналитиков, это закрывает разрыв «сломалось у пользователя — автор узнал через неделю»: автор получает уведомление сразу, в момент падения, с готовым трейсом. Пока это в планах, но архитектурно всё для этого на дашборде уже лежит.
Ниже — производительность: время по фазам для пагинированных отчётов, топ самых тяжёлых, разбивка нагрузки. Отдельно — фоновая активность .pbix: открытия по ConceptualSchema, обновления моделей, объём записи в каталог. В самом низу — здоровье нод: процессор и память сервиса, задержки дисков, паспорт серверов с характеристиками.
Времени ушло прилично. Причём большую его часть съели не запросы, а вот эти неочевидные грабли: .pbix врёт про фазы, ошибки доставки лежат в другой таблице, время разъезжается на часовой пояс. Зато теперь, когда приходит «а почему мне не пришла рассылка», ответ находится за пятнадцать секунд. Раньше на это уходило полдня раскопок в логах.
Если соберётесь повторять — главное, держите в голове, что .pbix живёт по своим правилам, не по правилам .rdl. Это сэкономит вам тот самый день, который потратил я.
Пишите вопросы, что вспомню — отвечу. Надеюсь, будет в помощь.
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.