How To Monitor SSRS Report Execution Times With The Data Collector

How To Monitor SSRS Report Execution Times With The Data Collector

The SQL Server Data Collector is like a Swiss army knife sometimes. So much stuff you can do with it. The challenge is to know how. So how do we use it to monitor SSRS report execution times and know your SLAs are up and about?

That’s what we’re going to look at now.

This article assumes working knowledge of the following:

  • The SSRS ReportServer Database.
  • The SQL Server Data Collector, or SSDC for short.
  • And by consequence, the SQL Server Management Data Warehouse, of MDW for short.

All the code shown is mean to be idempotent enough to be included in an SSDT Database Project, either as a model object or in the Post-Deployment Script itself. Therefore, remember to replace the SQLCMD variables, like $(SomeDB), with proper names if you’re running this outside of an SSDT Deployment.

Create A Delta Control Table

You’ll need this to ensure the Data Collector is only bringing in the newest rows from the SSRS Catalog, as opposed to the whole table with each snapshot. You can create this table straight on ReportServer database, but I strongly recommend you create a separate Monitoring database on the same server to put your own code in. The code below can be run on its own or added as a Table object in an SSDT Database Project.

CREATE TABLE [dbo].[SSRSDataCollectorStatus]
(
	LastTimeStart DATETIME NOT NULL PRIMARY KEY
)

Seed The Delta Control Table

This allows the future collection code not to worry about the table being empty or not. You only need to run this code once. You can also tuck it into an SSDT Post-Deployment Script.

PRINT 'Seeding the [SSRSDataCollectorStatus] control table.';
GO

WITH Seed AS
(
	SELECT
		CAST(0 AS DATETIME) AS LastTimeStart
)
MERGE INTO dbo.SSRSDataCollectorStatus AS T
USING Seed AS S
ON 1 = 1
WHEN NOT MATCHED BY TARGET THEN
	INSERT (LastTimeStart)
	VALUES (LastTimeStart)
;
GO

Create Code To Retrieve Deltas

This sample Stored Procedure will be used by the SSDC to import deltas of the ExecutionLog view and Catalog table, both from the ReportServer database, into the MDW. Each single execution will identify and return the latest delta. Note that, for lack of a proper ascending unique identifier in the ExecutionLog view, we have to default to the next best thing, the TimeStart column.

The code below is meant to be added as a Stored Procedure object in a separate SSDT Database Project. You’ll also need to add a database reference to a dacpac of the ReportServer database. See How To Reference SQL Server Data Collector’s Management Data Warehouse In An SSDT Database Project for an example.

If you’re running this on your own, remember to replace the $(ReportServer) SQLCMD variable with the name of your ReportServer database.

CREATE PROCEDURE [dbo].[usp_CollectSSRSExecutionLog]
AS

/* fetch the previous delta threshold */
DECLARE @DeltaStart DATETIME =
(
	SELECT
		S.LastTimeStart
	FROM
		dbo.SSRSDataCollectorStatus AS S
);

/* fetch the current delta threshold */
DECLARE @DeltaEnd DATETIME =
(
	SELECT
		TimeStart
	FROM
		[$(ReportServer)].dbo.ExecutionLog
	ORDER BY
		TimeStart DESC
	OFFSET 0 ROWS
	FETCH NEXT 1 ROW ONLY
);

/* return the current delta */
SELECT
	E.[InstanceName],
    E.[ReportID],
    E.[UserName],
    E.[RequestType],
    E.[Format],
    CAST(E.[Parameters] AS NVARCHAR(4000)) AS [Parameters],
    E.[TimeStart],
    E.[TimeEnd],
    E.[TimeDataRetrieval],
    E.[TimeProcessing],
    E.[TimeRendering],
    E.[Source],
    E.[Status],
    E.[ByteCount],
    E.[RowCount],
	C.[Name],
	C.[Type]
FROM
	[$(ReportServer)].dbo.ExecutionLog AS E
	INNER JOIN [$(ReportServer)].dbo.Catalog AS C
		ON C.ItemID = E.ReportID
WHERE
	TimeStart > @DeltaStart
	AND TimeStart <= @DeltaEnd
;

/* replace the delta threshold */
UPDATE dbo.SSRSDataCollectorStatus
SET
	LastTimeStart = @DeltaEnd
;

GO

Create A Data Collector Set

We’ll be calling it SSRS Catalog Information. You can use the SSDC interface for this, or you can just customize the code below at your will. This code will check if the Data Collector Set already exists, and if so, will try to stop it, so we can upgrade its Data Collector Items properly.

PRINT 'Stopping the [SSRS Catalog Information] Data Collector Set...';
GO

DECLARE @CollectionSetName SYSNAME = N'SSRS Catalog Information';
DECLARE @CollectionSetID INT =
(
	SELECT
		collection_set_id
	FROM
		msdb.dbo.syscollector_collection_sets WHERE name = @CollectionSetName
);

IF @CollectionSetID IS NOT NULL
BEGIN
	EXECUTE msdb.dbo.sp_syscollector_stop_collection_set
		@collection_set_id = @CollectionSetID;
END
GO

PRINT 'Creating the [SSRS Catalog Information] Data Collector Set...';
GO

/* create the new collector set if it doesn't exist yet */
DECLARE @CollectionSetName SYSNAME = N'SSRS Catalog Information';
DECLARE @CollectionSetID INT = NULL;
DECLARE @CollectionSetUID UNIQUEIDENTIFIER = NULL;
SELECT
    @CollectionSetID = collection_set_id,
    @CollectionSetUID = collection_set_uid
FROM
    msdb.dbo.syscollector_collection_sets WHERE name = @CollectionSetName

IF @CollectionSetID IS NULL
BEGIN

    /* grab the schedule identifier we are going to use */
    DECLARE @ScheduleUID UNIQUEIDENTIFIER =
    (
        SELECT schedule_uid
        FROM msdb.dbo.sysschedules
        WHERE name = 'CollectorSchedule_Every_5min'
    );

    EXECUTE msdb.dbo.sp_syscollector_create_collection_set
        @name = @CollectionSetName,
        @collection_mode = 1, /* non-cached mode */
        @description = N'Records SSRS Catalog Event Information.',
        @days_until_expiration = 30,
        @schedule_uid = @ScheduleUID,
        @collection_set_id = @CollectionSetID OUTPUT,
        @collection_set_uid = @CollectionSetUID OUTPUT

END;
GO

Create A Data Collector Item

The code below with create a Data Collector Item named SSRS Execution Log and add it to the SSRS Catalog Information Data Collector Set. This code is meant to be used in an SSDT Post-Deployment Script, but you can run this on your own too. If you do, remember to replace the $(DatabaseName) SQLCMD variable with the name of the database where you created the usp_CollectSSRSExecutionLog Stored Procedure.

PRINT 'Creating the [SSRS Execution Log] Data Collector Item...';
GO

/* find the parent collector set */
DECLARE @CollectionSetName SYSNAME = N'SSRS Catalog Information';
DECLARE @CollectionSetID INT = NULL;
DECLARE @CollectionSetUID UNIQUEIDENTIFIER = NULL;
SELECT
    @CollectionSetID = collection_set_id,
    @CollectionSetUID = collection_set_uid
FROM
    msdb.dbo.syscollector_collection_sets WHERE name = @CollectionSetName

-- get the collector type identifier we are going to use
DECLARE @CollectorTypeUID UNIQUEIDENTIFIER =
(
    SELECT collector_type_uid
    FROM msdb.dbo.syscollector_collector_types
    WHERE name = 'Generic T-SQL Query Collector Type'
);

/* create the [SSIS Event Messages] Data Collector Item */
DECLARE @CollectionItemName SYSNAME = 'SSRS Execution Log';
DECLARE @CollectionItemID INT = NULL;

/* first we clear the old definition */
SELECT
	@CollectionItemID = collection_item_id
FROM
	msdb.dbo.syscollector_collection_items
WHERE
	name = @CollectionItemName;

IF @CollectionItemID IS NOT NULL
EXECUTE msdb.dbo.sp_syscollector_delete_collection_item
    @collection_item_id = @CollectionItemID;

/* the we add the new definition */
DECLARE @Parameters XML =
'EXECUTE [dbo].[usp_CollectSSRSExecutionLog];
SSRSCatalog_ExecutionLog
$(DatabaseName)    
';
EXECUTE msdb.dbo.sp_syscollector_create_collection_item
    @name = @CollectionItemName,
    @parameters = @Parameters,
    @collection_item_id = @CollectionItemID OUTPUT,
    @collection_set_id = @CollectionSetID,
    @collector_type_uid = @CollectorTypeUID
;
GO

Restart The Data Collector Set

Well, if you really want to.

The code below is meant to be added at the end of your SSDT Post-Deployment Script. It will start the SSRS Catalog Information Data Collector Set, but only if a $(EnableDataCollector) SQLCMD variable is set to True. You can run this on your own if you remove all the extra bits of SSDT.

IF '$(EnableDataCollector)' = 'True'
BEGIN
	PRINT 'Starting the [SSRS Catalog Information] Data Collector Set...';
END
GO

IF '$(EnableDataCollector)' = 'True'
BEGIN
	DECLARE @CollectionSetName SYSNAME = N'SSRS Catalog Information';
	DECLARE @CollectionSetID INT =
	(
		SELECT
			collection_set_id
		FROM
			msdb.dbo.syscollector_collection_sets WHERE name = @CollectionSetName
	);

	IF @CollectionSetID IS NOT NULL
	BEGIN
		EXECUTE msdb.dbo.sp_syscollector_start_collection_set
			@collection_set_id = @CollectionSetID;
	END
END
GO

Create Code To Report On The MDW

Your data will now be available in the [custom_snapshots].[SSRSCatalog_ExecutionLog] table in your designated MDW database. The sample T-SQL below will calculate a couple of measures over the gathered data. You can use this as a base for your SLA related reports.

SELECT
	T.Name,
	COUNT (*) AS ExecutionCount,
	SUM (T.TimeDataRetrieval) / COUNT (*) AS AvgTimeDataRetrieval,
	SUM (T.TimeProcessing) / COUNT (*) AS AvgTimeProcessing,
	SUM (T.TimeRendering) / COUNT (*) AS AvgTimeRendering
FROM
	[custom_snapshots].[SSRSCatalog_ExecutionLog] AS T
WHERE
	T.Type = 2
	AND T.TimeStart = @TimeStart
GROUP BY
	T.Name

Celebrate

I hope this has helped getting that project of yours going. If you have any suggestions, please feel free to comment and I’ll revise the post accordingly.

Jorge Candeias's Picture

About Jorge Candeias

Jorge helps organizations build high-performing solutions on the Microsoft tech stack.

London, United Kingdom https://jorgecandeias.github.io