The Jerusalem PostStudent opens fire outside school in Turkey, eight pupils wounded, NTV reportsDaily MaverickStudent opens fire outside school in Turkey, eight pupils wounded, NTV reportsוואלהבמהלך חג הסוכות תוגבל כניסה למטיילים במשטחי אימונים בדרוםBollywood HungamaEXCLUSIVE: Ajay Devgn-Rohit Jugraj’s horror thriller titled SuryasparshInquirer EntertainmentJopay Paguia ‘respects’ Rochelle Pangilinan, but stands firm on her work ethicInquirerAgusan solon pushes national soil strategy through SUCsХабрКак в игровых студиях принимаются технические решения, когда на кону денюжкиCollider‘Marvel’s Wolverine’ Officially Changes Controversial Gameplay Feature After Fan BacklashSouth China Morning PostCan China’s grain reserves protect food security against El Nino?The South AfricanAircraft crash reported near Morningstar Airfield on the N7RTL BoulevardRekenkamer: Van Weel zette Kamer op verkeerde been over 10.000 onbehandelde aangiftenGhaflaBoundaries And Public Office: Omanga’s School Attire Trend Sparks Debate Online
The Daily Newsstand · Free, Always
Tuesday, September 22, 2026

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

Translate

К этой статье меня подтолкнула причина донельзя прозаическая. Сижу недавно на брифинге, и прилетает жалоба: сервер отчётов работает из рук вон плохо. Сервер при этом не мой, в орбиту обслуживания он не входил, а тут пришлось вспомнить, как оно всё устроено у 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 dashboardNothing. Вписали имя один раз — и забыли.

Шаг 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. Это сэкономит вам тот самый день, который потратил я.

Пишите вопросы, что вспомню — отвечу. Надеюсь, будет в помощь.

View the original on Хабр

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.