Дашборд для SSRS и Power BI Report Server, которого нет ни у кого: собираем сами и забираем готовым

от автора

Скажу честно: к этой статье меня подтолкнула причина донельзя прозаическая. Сижу недавно на брифинге, и прилетает жалоба — сервер отчётов работает из рук вон плохо. Сервер при этом не мой, в орбиту обслуживания он не входил, да и сам я последнее время вожусь с публикой помоложе и помоднее, Superset и FineBI. А тут пришлось тряхнуть стариной и вспомнить, как оно всё устроено у SSRS. Вспомнил. Заодно собрал то, чего мне самому когда-то не хватало, — и решил, что пора всё это выложить в одну статью.

Отчётный сервер — штука незаметная ровно до того момента, пока кто-нибудь из бизнеса не напишет: «а почему мне со вчерашнего дня не приходит утренняя рассылка». И вот ты сидишь, открываешь портал, видишь, что подписка вроде есть, вроде активна, а письма не уходят. Лезешь в логи. А логов-то нормальных и нет — есть таблица где-то внутри базы, в которую никто никогда не заглядывал.

Знакомо? Если вы держите 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_opensFROM ReportServer.dbo.ExecutionLog3WHERE 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.ErrorsInPeriodFROM ReportServer.dbo.Subscriptions sJOIN ReportServer.dbo.[Catalog] c ON c.ItemID = s.Report_OIDLEFT JOIN ReportServer.dbo.Users u ON u.UserID = s.OwnerIDINNER 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.SubscriptionIDWHERE she.ErrorsInPeriod > 0ORDER 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_utcWHERE 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 ASSELECT *,    DATEADD(HOUR, DATEDIFF(HOUR, GETDATE(), GETUTCDATE()), TimeStart) AS TimeStartUtcFROM 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]GOCREATE OR ALTER VIEW dbo.ExecutionLog3_utc ASSELECT *,    DATEADD(HOUR, DATEDIFF(HOUR, GETDATE(), GETUTCDATE()), TimeStart) AS TimeStartUtcFROM dbo.ExecutionLog3;GOCREATE OR ALTER VIEW dbo.SubscriptionHistory_utc ASSELECT *,    DATEADD(HOUR, DATEDIFF(HOUR, GETDATE(), GETUTCDATE()), StartTime) AS StartTimeUtcFROM dbo.SubscriptionHistory;GOCREATE OR ALTER VIEW dbo.Subscriptions_utc ASSELECT *,    DATEADD(HOUR, DATEDIFF(HOUR, GETDATE(), GETUTCDATE()), LastRunTime) AS LastRunTimeUtcFROM 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 dashboardNothing. Вписали имя один раз — и забыли.

Шаг 5. Проверить вспомогательные переменные

Ещё две переменные работают из коробки, трогать обычно не нужно:

  • report — выпадающий список всех отчётов (тянется из Catalog), фильтрует панели по конкретному отчёту. __ALL__ показывает всё.

  • fail_class — скрытый переключатель классов сбоев (.rdl / открытия .pbix / refresh) для фильтрации.

После этих пяти шагов дашборд оживёт полностью: SQL-панели увидят view, инфраструктурные — метрики нод, провал в историю подписки заработает.

Где забрать сборку

Весь комплект для развёртывания я выложил в исходной версии этого разбора у себя в блоге — там он лежит файлами, которые можно сразу скачать:

  • Основной дашборд — 21 панель: счётчики сбоев, таблицы подписок и отчётов с трейсом, производительность, фоновая нагрузка, здоровье нод.

  • Дашборд истории подписки — открывается по клику из таблицы упавших подписок, показывает расшифрованные ошибки доставки.

  • Скрипт создания view — три UTC-view, выполнить в базе ReportServer (Шаг 1).

  • Конфиг windows_exporter — рабочий конфиг с включённым коллектором process (Шаг 2).

Забрать всё одним пакетом — в оригинале статьи на моем сайте в блоге. В дашбордах нет ни реальных хостов, ни строк подключения — datasource подставите свои при импорте.

Что в итоге получилось

Дашборд, который отвечает на рабочие вопросы вместо показа безликих графиков.

Сверху — счётчики сбоев: сколько подписок упало, сколько отчётов не открылось, сколько подписок осиротело без владельца. Дальше — таблицы с трейсом: какой отчёт, какая ошибка, кто запускал, кто последним менял. Этот трейс удобно передать разработчику отчёта — вместо «у меня не работает» он получает конкретный код ошибки и время.

Тут напрашивается следующий шаг, до которого у меня пока не дошли руки. Раз в таблице уже есть ModifiedByID — кто последним правил отчёт, — можно навесить на эти панели алертинг Grafana и при сбое автоматически дёргать того, кто за отчёт отвечает. Если отчётами занимается команда аналитиков, это закрывает разрыв «сломалось у пользователя — автор узнал через неделю»: автор получает уведомление сразу, в момент падения, с готовым трейсом. Пока это в планах, но архитектурно всё для этого на дашборде уже лежит.

Ниже — производительность: время по фазам для пагинированных отчётов, топ самых тяжёлых, разбивка нагрузки. Отдельно — фоновая активность .pbix: открытия по ConceptualSchema, обновления моделей, объём записи в каталог. И в самом низу — здоровье нод: процессор и память сервиса, задержки дисков, паспорт серверов с характеристиками.

Времени ушло прилично, чего уж там. Причём бо́льшую его часть съели не запросы, а вот эти неочевидные грабли: .pbix врёт про фазы, ошибки доставки лежат в другой таблице, время разъезжается на часовой пояс. Зато теперь, когда приходит «а почему мне не пришла рассылка», ответ находится за пятнадцать секунд. Раньше на это уходило полдня раскопок в логах.

Если соберётесь повторять — главное, держите в голове, что .pbix живёт по своим правилам, не по правилам .rdl. Это сэкономит вам тот самый день, который потратил я.

А чем вы смотрите за своими отчётными серверами — или они у вас живут как чёрный ящик, пока не прилетит «рассылка не пришла»? Любопытно, кто как решает.

ссылка на оригинал статьи https://habr.com/ru/articles/1047918/