/**********************************************************************/
/* INSTMDW.SQL                                                        */
/*                                                                    */
/* Installs the tables and stored procedures necessary for            */
/* supporting the Management Data Warehouse in Data Collector         */
/*                                                                    */
/* MDW database is created by Setup and this script is run in the     */
/* context of the created MDW database                                */
/*                                                                    */
/* To run this script manually on a MDW database:                     */
/* "sqlcmd -d MDW -i instmdw.sql"                                     */
/*                                                                    */
/*                                                                    */
/* Copyright Microsoft, Inc. 2006                                     */
/* All Rights Reserved.                                               */
/*                                                                    */
/**********************************************************************/

PRINT ''
PRINT '----------------------------------'
PRINT 'Starting execution of INSTMDW.SQL'
PRINT '----------------------------------'
GO

-- This version of instmdw should be executed only against 10.0 servers or higher
IF (@@microsoftversion / 0x01000000) < 10
BEGIN
    RAISERROR('Management Data Warehouse database can only be installed on an instance of SQL Server 2008 or higher.', 21, 127) WITH LOG 
END
GO

-- SQL Server Express Edition does not support a management data warehouse.
DECLARE @retval sql_variant
SELECT @retval = (SELECT SERVERPROPERTY('EngineEdition'))
IF (@retval = 4) -- 4: Express
BEGIN
    RAISERROR(14713, 20, -1) WITH LOG 
END
GO

-- Take the database to single mode during the installation
PRINT ''
PRINT 'Taking database to single user mode'

DECLARE @dbname sysname
SELECT @dbname = QUOTENAME(DB_NAME())

-- Check if someone has turned async statistics autoupdate on
DECLARE @async_auto_stat bit
SELECT @async_auto_stat = is_auto_update_stats_async_on
FROM sys.databases
WHERE database_id = DB_ID()

CREATE TABLE #tmp_auto_mode (auto_mode bit)

IF (@async_auto_stat = 1)       -- if yes, turn it off while install
BEGIN
    PRINT 'Disabling asynchronous auto statistics while database in single user mode ...'
    DECLARE @sql_async_autostat_off nvarchar(256)
    SET @sql_async_autostat_off = 'ALTER DATABASE ' + @dbname + 
                                            ' SET AUTO_UPDATE_STATISTICS_ASYNC OFF'

    EXEC sp_executesql @sql_async_autostat_off

    -- check for all the currently running background statistics jobs
    -- and kill them
    DECLARE @stats_job_id nvarchar(10)
    DECLARE @sql_kill_stats_job nvarchar(256)

    DECLARE stats_jobs_id_cursor CURSOR READ_ONLY FOR
    SELECT CONVERT(nvarchar(10), job_id)
    FROM sys.dm_exec_background_job_queue
    WHERE database_id = DB_ID()

    OPEN stats_jobs_id_cursor
    FETCH NEXT FROM stats_jobs_id_cursor INTO @stats_job_id
    WHILE (@@FETCH_STATUS = 0)
    BEGIN
        SET @sql_kill_stats_job = 'KILL STATS JOB ' + @stats_job_id
        EXEC sp_executesql @sql_kill_stats_job
    END

    CLOSE stats_jobs_id_cursor
    DEALLOCATE stats_jobs_id_cursor

    -- save the fact that async_auto_stats was on
    INSERT INTO #tmp_auto_mode
    VALUES(1)
END

-- Now, put the database in single user mode
DECLARE @sql_db_single_mode nvarchar(256)
SET @sql_db_single_mode = 'ALTER DATABASE ' + @dbname +
                                    ' SET SINGLE_USER WITH ROLLBACK IMMEDIATE'
EXEC sp_executesql @sql_db_single_mode
GO

-- Set the right options
-- These are needed for the correct behavior of check constraint
SET ANSI_NULLS ON
SET ANSI_PADDING ON
SET ANSI_WARNINGS ON
SET ARITHABORT ON
SET CONCAT_NULL_YIELDS_NULL ON
SET NUMERIC_ROUNDABORT OFF
SET QUOTED_IDENTIFIER ON 
GO

/**********************************************************************/
/* Add an extended database property to identify database as a data   */
/* warehouse for the data collector.                                  */
/*                                                                    */
/* If a version already exists, check that we are not down grading    */
/* to a lower version.                                                */
/**********************************************************************/

--
-- This stored procedure compares two build numbers
-- It tokenizes the numbers by '.' and compares the portions from left to right
--
IF (OBJECT_ID(N'tempdb..#sp_compare_builds', 'P') IS NOT NULL)
BEGIN
    DROP PROCEDURE [tempdb]..[#sp_compare_builds]
END
GO 

CREATE PROCEDURE #sp_compare_builds
    @old            nvarchar(100),
    @new            nvarchar(100),
    @order          int OUTPUT      -- -1 if @old<@new, 0 if @old=@new, 1 if @old>@new
AS
BEGIN
    DECLARE @retVal int
    DECLARE @old_portion nvarchar(100)
    DECLARE @new_portion nvarchar(100)
    DECLARE @old_number int
    DECLARE @new_number int
    
    SET @old = NULLIF(@old+'.', '.')
    SET @new = NULLIF(@new+'.', '.')    
    SET @order = 0
    BEGIN TRY
        SET @retVal = 0
        WHILE ((@old IS NOT NULL) AND (@new IS NOT NULL))
        BEGIN
            --SELECT @old as old, @new as new
            DECLARE @old_token_mark int
            DECLARE @new_token_mark int
            SET @old_token_mark = CHARINDEX('.', @old)
            SET @new_token_mark = CHARINDEX('.', @new)
            
            -- get the first number in the version from the left
            SET @old_portion = LEFT(@old, @old_token_mark-1)
            SET @new_portion = LEFT(@new, @new_token_mark-1)
            
            -- trim the number from the left
            SET @old = NULLIF(SUBSTRING(@old, @old_token_mark+1, LEN(@old)-@old_token_mark), '')
            SET @new = NULLIF(SUBSTRING(@new, @new_token_mark+1, LEN(@new)-@new_token_mark), '')
            
            -- compare the portions you have
            SET @old_number = CONVERT(int, @old_portion)
            SET @new_number = CONVERT(int, @new_portion)
            
            IF (@old_number = @new_number)
            BEGIN
                CONTINUE
            END
            ELSE
            BEGIN -- We have a resolution, figure out who comes first and get out
                IF (@old_number < @new_number)
                BEGIN
                    SET @order = -1
                END
                ELSE
                BEGIN
                    SET @order = 1
                END
                
                BREAK
            END
        END
        
        RETURN (0)
    END TRY
    BEGIN CATCH
        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorSeverity  INT;
        DECLARE @ErrorState     INT;
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        DECLARE @ErrorProcedure NVARCHAR(200);
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorSeverity = ERROR_SEVERITY(),
               @ErrorState = ERROR_STATE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE(),
               @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');

        RAISERROR (14684, @ErrorSeverity, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);

        RETURN (1)
    END CATCH    
END
GO

DECLARE @prop_name sysname
DECLARE @new_value sql_variant
DECLARE @old_value sql_variant
SET @prop_name = 'Microsoft_DataCollector_MDW_Version'
SET @new_value = '10.0.1600.22'     -- This will be replaced at build time with the sql build number

SELECT @old_value = value
FROM fn_listextendedproperty(@prop_name, NULL, NULL, NULL, NULL, NULL, NULL)

IF (@old_value IS NOT NULL)
BEGIN
    DECLARE @order int
    DECLARE @old_value_char nvarchar(100)
    DECLARE @new_value_char nvarchar(100)
    SET @old_value_char = CONVERT(nvarchar(100), @old_value)
    SET @new_value_char = CONVERT(nvarchar(100), @new_value)

    -- check that we are not downgrading the database
    IF (@old_value_char <> '10.0.1600.22')   -- value only used during development
    BEGIN
        EXEC #sp_compare_builds @old_value_char, @new_value_char, @order OUTPUT
        
        IF (@order > 0) -- the old build version is older than the new, abort the script and kill the connection
        BEGIN
            -- Put the database back into multi user mode
            PRINT ''
            PRINT 'Restoring database to multi user mode before aborting the script'
            DECLARE @dbname sysname
            SET @dbname = QUOTENAME(DB_NAME())

            DECLARE @sql_db_multi_mode nvarchar(256)
            SET @sql_db_multi_mode = 'ALTER DATABASE ' + @dbname +
                                                ' SET MULTI_USER WITH ROLLBACK IMMEDIATE'
            EXEC sp_executesql @sql_db_multi_mode

            RAISERROR(14714, 21, 1, @old_value_char, @new_value_char) WITH LOG
        END
    END
    EXEC sp_dropextendedproperty @name = @prop_name
END            

EXEC sp_addextendedproperty
        @name = @prop_name,
        @value = @new_value
GO

DROP PROCEDURE #sp_compare_builds


/*
Procedure [#create_or_alter_primary_key_or_index]

Helper proc to create/update primary key constraints and nonclustered/clustered indexes. 
Avoids the need to duplicate object definition in separate creation and upgrade sections. 
Prevents cluttering up the script with repetitive logic to repeat these tasks every time 
we need to define an index or a primary key: 
  - Check for an existing object and skip the create if appropriate
  - Compare the definition of the existing object (clustered vs. nonclustered, number and 
    order of index key columns, ignore_dup_key bit, included vs. key columns, asc/desc 
    sort direction) to the desired index or constraint definition
  - Drop the existing constraint/index if it is malformed or out-of-date 
  - Drop nonclustered indexes before dropping clustered indexes
  - Drop referencing foreign keys before dropping a primary key
 
Parameters: 
    @table_schema - e.g. "snapshots"
    @table_name - e.g. "query_stats"
    @object_type - either "PRIMARY KEY" or "INDEX"
    @constraint_or_index_name - the PK or index name
    @ignore_dup_key - 1 to enable the IGNORE_DUP_KEY index option (default 0)
    @clustered - 1 to create a clustered index/PK (default 1)

The columns in the table are passed to this proc via this temp table: 
    CREATE TABLE #index_key_columns (
        key_ordinal         int IDENTITY, 
        constraint_name     sysname, 
        column_name         sysname, 
        is_included_column  bit, 
        is_descending_key   bit
    );

Example usage: 
    TRUNCATE TABLE #index_key_columns;
    INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
    VALUES 
        ('IDX_performance_counter_instances1', 'object_name', 0, 0), 
        ('IDX_performance_counter_instances1', 'counter_name', 0, 0), 
        ('IDX_performance_counter_instances1', 'instance_name', 1, 0);
    GO

    EXEC #create_or_alter_primary_key_or_index
        @table_schema = 'snapshots', @table_name = 'performance_counter_instances', 
        @object_type = 'INDEX', @constraint_or_index_name = 'IDX_performance_counter_instances1', 
        @ignore_dup_key = 0, @clustered = 0;
    GO

The TRUNCATE is typically not necessary, but is a good practice just in case two 
indexes have the same name. 
*/
IF OBJECT_ID ('tempdb..#create_or_alter_primary_key_or_index') IS NOT NULL 
BEGIN
    DROP PROC #create_or_alter_primary_key_or_index 
END;
GO
CREATE PROC #create_or_alter_primary_key_or_index 
    @table_schema sysname, 
    @table_name sysname, 
    @object_type sysname, 
    @constraint_or_index_name sysname, 
    @ignore_dup_key bit = 0, 
    @clustered bit = 1
AS
BEGIN
    RAISERROR (N'Validating %s [%s] on table [%s].[%s]...', 0, 1, 
        @object_type, @constraint_or_index_name, @table_schema, @table_name) WITH NOWAIT;

    -- Get the list of key columns and included columns that are supposed to be in 
    -- this index/PK constraint
    DECLARE @column_name sysname;
    DECLARE @is_included_column bit;
    DECLARE @is_descending_key bit;
    DECLARE @key_column_list nvarchar(max);
    DECLARE @include_column_list nvarchar(max);
    DECLARE @sql nvarchar(max);
    DECLARE @expected_column_count int;
    SET @key_column_list = '';
    SET @include_column_list = '';
    SET @expected_column_count = 0;
    DECLARE cols INSENSITIVE CURSOR FOR 
    SELECT column_name, is_included_column, is_descending_key
    FROM #index_key_columns
    WHERE constraint_name = @constraint_or_index_name
    ORDER BY key_ordinal ASC;
    
    OPEN cols;
    FETCH NEXT FROM cols INTO @column_name, @is_included_column, @is_descending_key;

    WHILE (@@FETCH_STATUS = 0)
    BEGIN
        IF (@is_included_column = 1)
        BEGIN
            SET @include_column_list = @include_column_list 
                + CASE WHEN LEN (@include_column_list) > 0 THEN ', ' ELSE '' END 
                + QUOTENAME (@column_name)
            IF (@object_type = 'PRIMARY KEY')
            BEGIN
                RAISERROR ('Error in script -- INCLUDEd columns are not allowed in a primary key', 21, 1);
            END
        END
        ELSE BEGIN
            SET @key_column_list = @key_column_list 
                + CASE WHEN LEN (@key_column_list) > 0 THEN ', ' ELSE '' END 
                + QUOTENAME (@column_name)
                + CASE @is_descending_key WHEN 1 THEN ' DESC' ELSE ' ASC' END;
        END;
        SET @expected_column_count = @expected_column_count + 1;
        
        FETCH NEXT FROM cols INTO @column_name, @is_included_column, @is_descending_key;
    END;
    CLOSE cols;
    DEALLOCATE cols;

    -- Compare the object definition to the expected definition
    DECLARE @unexpected_column_count int;
    DECLARE @total_column_count int;
    DECLARE @total_included_count int;
    DECLARE @out_of_order_key_count int;
    DECLARE @wrong_sort_direction_count int;
    DECLARE @wrong_included_column_count int;
    DECLARE @key_ordinal_base int;
    DECLARE @existing_constraint_or_index_name  sysname;
    DECLARE @current_ignore_dup_key bit;
    DECLARE @current_clustered bit;
    DECLARE @table_object_id int;

    -- Get the target table's object id
    SET @table_object_id = OBJECT_ID (QUOTENAME (@table_schema) + '.' + QUOTENAME (@table_name));

    -- Get the indentity value of the first column in this constraint
    SELECT @key_ordinal_base = MIN (key_ordinal) - 1
    FROM #index_key_columns
    WHERE constraint_name = @constraint_or_index_name;

    -- Check the constraint schema against expected schema, note any differences
    SELECT 
        @total_column_count = COUNT(*), 
        @wrong_included_column_count = SUM (
            CASE 
                WHEN ic.is_included_column != ckc.is_included_column THEN 1
                ELSE 0
            END), 
        @wrong_sort_direction_count = SUM (
            CASE 
                WHEN ic.is_descending_key != ckc.is_descending_key AND ckc.is_included_column = 0 THEN 1
                ELSE 0
            END), 
        @unexpected_column_count = SUM (
            CASE
                -- [#index_key_columns].[column_name] will be NULL for any unexpected columns
                WHEN ckc.column_name IS NULL THEN 1
                ELSE 0
            END), 
        @out_of_order_key_count = SUM (
            CASE 
                WHEN ic.key_ordinal != (ckc.key_ordinal - @key_ordinal_base) AND ckc.is_included_column = 0 THEN 1
                ELSE 0
            END), 
        @existing_constraint_or_index_name = MIN (i.name), 
        @current_ignore_dup_key = MAX (CASE WHEN i.[ignore_dup_key] = 1 THEN 1 ELSE 0 END), 
        @current_clustered = MAX (CASE WHEN i.type_desc = 'CLUSTERED' THEN 1 ELSE 0 END)
    FROM sys.indexes AS i
    INNER JOIN sys.index_columns AS ic ON 
        i.[object_id] = ic.[object_id] AND i.index_id = ic.index_id
    INNER JOIN sys.columns AS c ON 
        ic.[object_id] = c.[object_id] AND c.column_id = ic.column_id
    LEFT OUTER JOIN #index_key_columns AS ckc ON 
        c.name = ckc.column_name 
    WHERE 
        i.[object_id] = @table_object_id AND 
        ckc.constraint_name = @constraint_or_index_name AND 
        -- Match index on expected name unless it's a PK, in which case we'll just look for [is_primary_key]=1
        (i.name = @constraint_or_index_name OR (@object_type = 'PRIMARY KEY' AND i.is_primary_key = 1)) AND 
        i.is_hypothetical = 0;

    -- If the constraint doesn't yet exist, or if it exists but is defined incorrectly, we 
    -- need to create it (and may need to drop some related objects before we create it). 
    IF (@total_column_count != @expected_column_count OR 
        @wrong_included_column_count > 0 OR 
        @wrong_sort_direction_count > 0 OR 
        @unexpected_column_count > 0 OR 
        @out_of_order_key_count > 0 OR 
        @current_ignore_dup_key != @ignore_dup_key OR
        @current_clustered != @clustered)
    BEGIN
        -- If this is a clustered index or clustered primary key, drop any NC indexes. 
        -- The calling script will rebuild them later, and if we don't drop them before we 
        -- drop the existing clustered PK/index, we'll end up wasting time by rebuilding 
        -- the nonclustered indexes twice (first when the clustered index is dropped, then 
        -- again when it gets recreated). 
        IF (@clustered = 1)
        BEGIN
            DECLARE @nc_index_name sysname;

            RAISERROR (N'    Dropping nonclustered indexes before dropping the clustered index...', 0, 1) WITH NOWAIT;

            DECLARE nc_indexes INSENSITIVE CURSOR FOR 
            SELECT i.name AS nc_index_name
            FROM sys.indexes AS i
            WHERE i.type_desc = 'NONCLUSTERED' AND i.is_unique_constraint = 0 AND i.is_hypothetical = 0 
                AND i.is_primary_key = 0 AND i.[object_id] = @table_object_id
            OPEN nc_indexes;
            FETCH NEXT FROM nc_indexes INTO @nc_index_name;

            WHILE (@@FETCH_STATUS = 0)
            BEGIN
                SET @sql = N'DROP INDEX ' + QUOTENAME (@table_schema) + '.' + QUOTENAME (@table_name) 
                    + '.' + QUOTENAME (@nc_index_name) + ';';
                RAISERROR (N'    Dropping nonclustered index: %s', 0, 1, @sql) WITH NOWAIT;
                EXEC (@sql);
                FETCH NEXT FROM nc_indexes INTO @nc_index_name;
            END;
            CLOSE nc_indexes;
            DEALLOCATE nc_indexes;
        END;

        -- If this is a primary key referenced by foreign keys, we have to drop the foreign keys before 
        -- we can drop the PK.  We also have to drop the foreign keys if we are about to drop a clustered 
        -- primary key to "make room" for a clustered non-PK index. 
        IF ((@object_type = 'PRIMARY KEY') OR 
            (@object_type = 'INDEX' AND @clustered = 1 AND 
                EXISTS (SELECT * FROM sys.indexes AS i WHERE i.[object_id] = @table_object_id AND i.is_primary_key = 1 AND i.type_desc = 'CLUSTERED')))
        BEGIN
            DECLARE @fk_constraint_name sysname;
            DECLARE @fk_table_schema sysname;
            DECLARE @fk_table_name sysname;

            DECLARE fk_constraints INSENSITIVE CURSOR FOR 
            SELECT 
                OBJECT_SCHEMA_NAME (fk.parent_object_id) AS fk_table_schema,  
                OBJECT_NAME (fk.parent_object_id) AS fk_table_name,
                fk.name AS fk_constraint_name
            FROM sys.foreign_keys AS fk
            WHERE referenced_object_id = @table_object_id
            OPEN fk_constraints;
            FETCH NEXT FROM fk_constraints INTO @fk_table_schema, @fk_table_name, @fk_constraint_name;

            WHILE (@@FETCH_STATUS = 0)
            BEGIN
                RAISERROR (N'    Dropping foreign key [%s].[%s].[%s] because it references table [%s]...', 0, 1, 
                    @fk_table_schema, @fk_table_name, @fk_constraint_name, @table_name) WITH NOWAIT;
                SET @sql = N'ALTER TABLE ' + QUOTENAME (@fk_table_schema) + '.' + QUOTENAME (@fk_table_name) 
                    + ' DROP CONSTRAINT ' + QUOTENAME (@fk_constraint_name) + ';';
                EXEC (@sql);
                FETCH NEXT FROM fk_constraints INTO @fk_table_schema, @fk_table_name, @fk_constraint_name;
            END;
            CLOSE fk_constraints;
            DEALLOCATE fk_constraints;
        END;

        -- Drop the incorrect constraint, if it exists. 
        IF (@total_column_count > 0)
        BEGIN
            RAISERROR (N'    Incorrect existing definition (current %d vs. expected %d key columns, %d wrong included columns, %d wrong sort direction, %d unexpected columns, %d out-of-order columns)', 0, 1, 
                @total_column_count, @expected_column_count, @wrong_included_column_count, @wrong_sort_direction_count, @unexpected_column_count, @out_of_order_key_count) WITH NOWAIT;
                                
            -- Drop the existing malformed constraint or index so that we can recreate it correctly
            IF @object_type = 'PRIMARY KEY'
            BEGIN
                SET @sql = N'ALTER TABLE ' + QUOTENAME (@table_schema) + '.' + QUOTENAME (@table_name) 
                    + ' DROP CONSTRAINT ' + QUOTENAME (@existing_constraint_or_index_name) + ';';
            END
            ELSE BEGIN
                SET @sql = N'DROP INDEX ' + QUOTENAME (@table_schema) + '.' + QUOTENAME (@table_name) 
                    + '.' + QUOTENAME (@existing_constraint_or_index_name) + ';';
            END;
            RAISERROR (N'    Dropping existing object: %s', 0, 1, @sql) WITH NOWAIT;
            EXEC (@sql);
        END;
        
        -- If we are about to create a clustered PK index but a clustered (non-PK) index exists, 
        -- we must drop the clustered index before we can create the clustered PK (or vice versa). 
        -- Example: Table has a clustered index, and we need to convert an existing non-clustered 
        -- PK into a clustered PK. 
        IF (@clustered = 1)
        BEGIN
            DECLARE @is_primary_key bit;
            SET @existing_constraint_or_index_name = NULL;
            SELECT 
                @existing_constraint_or_index_name = i.name, 
                @is_primary_key = is_primary_key
            FROM sys.indexes AS i
            WHERE i.type_desc = 'CLUSTERED' AND i.is_hypothetical = 0 AND i.[object_id] = @table_object_id
            
            IF (@existing_constraint_or_index_name IS NOT NULL)
            BEGIN
                -- Drop the existing clustered index to make way for our clustered PK 
                -- (or drop the existing clustered PK to make way for our clustered index)
                IF (@is_primary_key = 1)
                BEGIN
                    SET @sql = N'ALTER TABLE ' + QUOTENAME (@table_schema) + '.' + QUOTENAME (@table_name) 
                        + ' DROP CONSTRAINT ' + QUOTENAME (@existing_constraint_or_index_name) + ';';
                END
                ELSE BEGIN
                    SET @sql = N'DROP INDEX ' + QUOTENAME (@table_schema) + '.' + QUOTENAME (@table_name) 
                        + '.' + QUOTENAME (@existing_constraint_or_index_name) + ';';
                END;
                RAISERROR (N'    Dropping existing clustered index: %s', 0, 1, @sql) WITH NOWAIT;
                EXEC (@sql);
            END;
        END;

        -- Finally, create the PK or index
        IF @object_type = 'PRIMARY KEY'
        BEGIN
            SET @sql = N'ALTER TABLE ' + QUOTENAME (@table_schema) + '.' + QUOTENAME (@table_name) 
                + ' ADD CONSTRAINT ' + QUOTENAME (@constraint_or_index_name) + ' PRIMARY KEY ' 
                + CASE @clustered WHEN 1 THEN 'CLUSTERED' ELSE 'NONCLUSTERED' END 
                + ' (' + @key_column_list + ')'
                + CASE @ignore_dup_key WHEN 1 THEN ' WITH (IGNORE_DUP_KEY=ON)' ELSE '' END 
                + ';';
        END
        ELSE BEGIN
            SET @sql = N'CREATE ' + CASE @clustered WHEN 1 THEN 'CLUSTERED' ELSE 'NONCLUSTERED' END 
                + ' INDEX ' + QUOTENAME (@constraint_or_index_name) + ' ON ' 
                + QUOTENAME (@table_schema) + '.' + QUOTENAME (@table_name) 
                + ' (' + @key_column_list + ')'
                + CASE WHEN LEN (@include_column_list) > 0 THEN ' INCLUDE (' + @include_column_list + ') ' ELSE '' END 
                + CASE @ignore_dup_key WHEN 1 THEN ' WITH (IGNORE_DUP_KEY=ON)' ELSE '' END 
                + ';';
        END;
        RAISERROR (N'    Creating new object: %s', 0, 1, @sql) WITH NOWAIT;
        EXEC (@sql);
    END;
END;
GO

-- Create the temp table used to pass column lists to helper proc [#create_or_alter_primary_key_or_index]
IF OBJECT_ID ('tempdb..#index_key_columns') IS NOT NULL
BEGIN
    DROP TABLE #index_key_columns;
END;
GO
CREATE TABLE #index_key_columns (
    key_ordinal         int IDENTITY, 
    constraint_name     sysname, 
    column_name         sysname, 
    is_included_column  bit, 
    is_descending_key   bit
);
GO


-- Start a transaction
BEGIN TRANSACTION InstMdwSql
GO


/**********************************************************************/
/* UPGRADE SECTION - UPDATE EXISTING OBJECTS                          */
/**********************************************************************/

--
-- >>> CTP5 -> CTP6 Upgrade
--

-- Update notable_query_plan
IF (OBJECT_ID(N'snapshots.notable_query_plan', 'U') IS NOT NULL)
BEGIN
    RAISERROR ('Updating table [snapshots].[notable_query_plan]...', 0, 1) WITH NOWAIT

    -- CTP6 added the [plan_generation_num] column to this table
    IF NOT EXISTS (SELECT column_id FROM sys.all_columns c WHERE c.name = N'plan_generation_num' AND c.object_id = OBJECT_ID(N'snapshots.notable_query_plan', 'U'))
    BEGIN
        ALTER TABLE [snapshots].[notable_query_plan]
            ADD [plan_generation_num] bigint DEFAULT 0 NOT NULL;
    END;
END;
GO

-- Update active_sessions_and_requests
-- Add a column
IF (OBJECT_ID(N'snapshots.active_sessions_and_requests', 'U') IS NOT NULL)
BEGIN
    RAISERROR ('Updating table [snapshots].[active_sessions_and_requests]...', 0, 1) WITH NOWAIT

    IF NOT EXISTS (SELECT column_id FROM sys.all_columns c WHERE c.name = N'is_blocking' AND c.object_id = OBJECT_ID(N'snapshots.active_sessions_and_requests', 'U'))
    BEGIN
        ALTER TABLE [snapshots].[active_sessions_and_requests] 
            ADD [is_blocking] bit DEFAULT 0 NOT NULL
    END;
END;
GO

-- Update performance_counter_instances
-- Add Unique constraint for [path] column
-- This constraint creation generates a warning. We will deal with the warning when fix for Defect# 130259 is provided
IF (OBJECT_ID(N'snapshots.performance_counter_instances', 'U') IS NOT NULL)
BEGIN
    RAISERROR ('Updating table [snapshots].[performance_counter_instances]...', 0, 1) WITH NOWAIT
    
    IF NOT EXISTS (SELECT index_id FROM sys.indexes WHERE object_id = OBJECT_ID('snapshots.performance_counter_instances', 'U') AND name = N'UN_performance_counter_path')
    BEGIN
        ALTER TABLE [snapshots].[performance_counter_instances] 
		    ADD CONSTRAINT [UN_performance_counter_path] UNIQUE
		    (
			    [path]
		    ) WITH (IGNORE_DUP_KEY = ON)
		    ON [PRIMARY];
	END;
END;
GO

-- Update performance_counter_instances
IF (OBJECT_ID(N'snapshots.performance_counter_values', 'U') IS NOT NULL)
BEGIN
    RAISERROR ('Updating table [snapshots].[performance_counter_values]...', 0, 1) WITH NOWAIT

    IF EXISTS (SELECT c.system_type_id FROM sys.columns c WHERE c.object_id = OBJECT_ID('snapshots.performance_counter_values', 'U') AND c.name = N'formatted_value' AND c.system_type_id = 127)
    BEGIN
        RAISERROR ('Changing [formatted_value] data type to float...', 0, 1) WITH NOWAIT
        
        -- This index will be recreated later (after the table definition)
        IF EXISTS (SELECT * FROM sys.indexes AS i WHERE i.name = 'IDX_performance_counter_values1' AND [object_id] = OBJECT_ID ('snapshots.performance_counter_values'))
        BEGIN
            DROP INDEX [IDX_performance_counter_values1] ON [snapshots].[performance_counter_values];
        END;
       
        ALTER TABLE [snapshots].[performance_counter_values]
            ALTER COLUMN [formatted_value] float NOT NULL;
    END;
END;
GO

-- Change int log_id columns to bigint 
IF EXISTS (
    SELECT * 
    FROM sys.columns AS c
    INNER JOIN sys.types AS t ON c.system_type_id = t.system_type_id
    WHERE [object_id] = OBJECT_ID ('core.snapshots_internal')
        AND c.name = 'log_id' AND t.name = 'int'
    )
BEGIN
    RAISERROR ('Changing [core].[snapshots_internal].[log_id] column datatype from int to bigint...', 0, 1) WITH NOWAIT
    ALTER TABLE [core].[snapshots_internal] ALTER COLUMN [log_id] bigint NOT NULL;
END;
GO
IF (OBJECT_ID ('core.snapshots') IS NOT NULL AND OBJECT_ID ('core.snapshots_internal') IS NOT NULL)
BEGIN
    -- Refresh the view to ensure that it reflects the datatype change
    EXEC sp_refreshview 'core.snapshots'
END
GO

--
-- >>> CTP6 -> CTP6 Refresh/RC0 Upgrade
--
-- Our query stats data collection query merges query stats from sys.dm_exec_query_stats (completed query stats) 
-- and sys.dm_exec_requests (in-progress query stats).  We may not have access to all query stats for any queries 
-- that were only visible as in-progress queries in dm_exec_requests.  For example, this DMV doesn't expose CLR 
-- stats. Some of the columns in our query_stats table must therefore tolerate NULLs. 
IF EXISTS (
    SELECT c.* 
    FROM INFORMATION_SCHEMA.TABLES AS t
    INNER JOIN INFORMATION_SCHEMA.COLUMNS AS c ON c.TABLE_SCHEMA = t.TABLE_SCHEMA AND c.TABLE_NAME = t.TABLE_NAME 
    WHERE t.TABLE_SCHEMA = 'snapshots' AND t.TABLE_NAME = 'query_stats'
        AND c.COLUMN_NAME = 'snapshot_clr_time' AND c.IS_NULLABLE = 'NO'
)
BEGIN
    RAISERROR ('Making [snapshots].[query_stats] columns NULLable...', 0, 1) WITH NOWAIT
    ALTER TABLE snapshots.query_stats ALTER COLUMN min_clr_time bigint NULL;
    ALTER TABLE snapshots.query_stats ALTER COLUMN max_clr_time bigint NULL;
    ALTER TABLE snapshots.query_stats ALTER COLUMN total_clr_time bigint NULL;
    ALTER TABLE snapshots.query_stats ALTER COLUMN snapshot_clr_time bigint NULL;
    ALTER TABLE snapshots.query_stats ALTER COLUMN min_worker_time bigint NULL;
    ALTER TABLE snapshots.query_stats ALTER COLUMN min_physical_reads bigint NULL;
    ALTER TABLE snapshots.query_stats ALTER COLUMN min_logical_writes bigint NULL;
    ALTER TABLE snapshots.query_stats ALTER COLUMN min_logical_reads bigint NULL;
    ALTER TABLE snapshots.query_stats ALTER COLUMN min_elapsed_time bigint NULL;
END;
GO

-- Add the [snapshot_execution_count] column (new to CTP6 Refresh) to [query_stats]
IF OBJECT_ID ('snapshots.query_stats', 'U') IS NOT NULL AND NOT EXISTS (
    SELECT c.* 
    FROM INFORMATION_SCHEMA.TABLES AS t
    INNER JOIN INFORMATION_SCHEMA.COLUMNS AS c ON c.TABLE_SCHEMA = t.TABLE_SCHEMA AND c.TABLE_NAME = t.TABLE_NAME 
    WHERE t.TABLE_SCHEMA = 'snapshots' AND t.TABLE_NAME = 'query_stats'
        AND c.COLUMN_NAME = 'snapshot_execution_count'
)
BEGIN
    RAISERROR ('Adding [snapshot_execution_count] column to [snapshots].[query_stats]...', 0, 1) WITH NOWAIT
    ALTER TABLE snapshots.query_stats ADD [snapshot_execution_count] bigint NULL;
END;
GO

/**********************************************************************/
/* CORE SCHEMA                                                        */
/**********************************************************************/
PRINT ''
PRINT 'Create schema core...'
GO
IF (SCHEMA_ID('core') IS NULL)
BEGIN
    DECLARE @sql nvarchar(128)
    SET @sql = 'CREATE SCHEMA core'
    EXEC sp_executesql @sql
END
GO

-- SUPPORTED_COLLECTOR_TYPES
--
PRINT ''
GO
IF (OBJECT_ID(N'[core].[supported_collector_types_internal]', 'U') IS NULL)
BEGIN
    PRINT 'Creating table [core].[supported_collector_types_internal]...'
    CREATE TABLE [core].[supported_collector_types_internal] (
        collector_type_uid            uniqueidentifier NOT NULL,
    ) ON [PRIMARY]

END
GO

-- supported_collector_types_internal.PK_supported_collector_types_internal
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('PK_supported_collector_types_internal', 'collector_type_uid', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'core', @table_name = 'supported_collector_types_internal', 
    @object_type = 'PRIMARY KEY', @constraint_or_index_name = 'PK_supported_collector_types_internal', 
    @ignore_dup_key = 0, @clustered = 1;
GO


IF (NOT OBJECT_ID(N'core.supported_collector_types', 'V') IS NULL)
BEGIN
    PRINT 'Dropping view [core].[supported_collector_types]...'
    DROP VIEW core.supported_collector_types
END
GO

PRINT 'Creating view [core].[supported_collector_types]...'
GO
CREATE VIEW core.supported_collector_types
AS 
    SELECT collector_type_uid
    FROM core.supported_collector_types_internal
GO

-- SOURCE_INFO
--
IF (OBJECT_ID(N'[core].[source_info_internal]', 'U') IS NULL)
BEGIN
    PRINT 'Creating table [core].[source_info_internal]...'
    CREATE TABLE [core].[source_info_internal] (
        source_id                   int IDENTITY NOT NULL,
        collection_set_uid          uniqueidentifier NOT NULL,  -- GUID of the collection set that loads the data
        instance_name               sysname COLLATE Latin1_General_CI_AI NOT NULL, -- the name of the machine the data is uploaded from
        days_until_expiration       smallint NOT NULL,          -- how many days data from this source should be kept in the warehouse. 0 indicates forever
        operator                    sysname NOT NULL,           -- login name of the principal who is uploading the data
        
        CONSTRAINT [UQ_collection_set_uid_instance_name] UNIQUE (collection_set_uid, instance_name, operator)
    ) ON [PRIMARY]
END
GO

-- source_info_internal.PK_source_info_internal
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('PK_source_info_internal', 'source_id', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'core', @table_name = 'source_info_internal', 
    @object_type = 'PRIMARY KEY', @constraint_or_index_name = 'PK_source_info_internal', 
    @ignore_dup_key = 0, @clustered = 1;
GO


-- SNAPSHOT_TIMETABLE
--
IF (OBJECT_ID(N'[core].[snapshot_timetable_internal]', 'U') IS NULL)
BEGIN
    PRINT 'Creating table [core].[snapshot_timetable_internal]...'
    CREATE TABLE [core].[snapshot_timetable_internal] (
        snapshot_time_id            int IDENTITY NOT NULL,
        snapshot_time                datetimeoffset(7) NOT NULL,
            ) ON [PRIMARY]
END
GO

-- snapshot_timetable_internal.PK_snapshots_timetable_internal
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('PK_snapshots_timetable_internal', 'snapshot_time_id', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'core', @table_name = 'snapshot_timetable_internal', 
    @object_type = 'PRIMARY KEY', @constraint_or_index_name = 'PK_snapshots_timetable_internal', 
    @ignore_dup_key = 0, @clustered = 1;
GO

-- snapshot_timetable_internal.IDX_snapshot_time
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('IDX_snapshot_time', 'snapshot_time', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'core', @table_name = 'snapshot_timetable_internal', 
    @object_type = 'INDEX', @constraint_or_index_name = 'IDX_snapshot_time', 
    @ignore_dup_key = 0, @clustered = 0;
GO


-- SNAPSHOTS
--
IF (OBJECT_ID(N'[core].[snapshots_internal]', 'U') IS NULL)
BEGIN
    PRINT 'Creating table [core].[snapshots_internal]...'
    CREATE TABLE [core].[snapshots_internal] (
        snapshot_id                    int IDENTITY NOT NULL,
        snapshot_time_id               int NOT NULL,
        source_id                      int NOT NULL,
        log_id                         bigint NOT NULL,             -- reference to the log table
    ) ON [PRIMARY]

    CREATE STATISTICS [STAT_snapshots_internal2] ON [core].[snapshots_internal](
        [snapshot_time_id], 
        [source_id]
    )

    CREATE STATISTICS [STAT_snapshots_internal3] ON [core].[snapshots_internal](
        [snapshot_time_id], [snapshot_id], [source_id])

END
GO

-- snapshots_internal.PK_snapshots_internal
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('PK_snapshots_internal', 'snapshot_id', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'core', @table_name = 'snapshots_internal', 
    @object_type = 'PRIMARY KEY', @constraint_or_index_name = 'PK_snapshots_internal', 
    @ignore_dup_key = 0, @clustered = 1;
GO

-- snapshots_internal.IDX_snapshot_time_id
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('IDX_snapshot_time_id', 'snapshot_time_id', 0, 0), 
    ('IDX_snapshot_time_id', 'source_id', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'core', @table_name = 'snapshots_internal', 
    @object_type = 'INDEX', @constraint_or_index_name = 'IDX_snapshot_time_id', 
    @ignore_dup_key = 0, @clustered = 0;
GO

-- snapshots_internal.FK_snapshots_source_info
IF OBJECT_ID ('core.FK_snapshots_source_info', 'F') IS NULL
BEGIN
    RAISERROR ('Creating foreign key [FK_snapshots_source_info] on core.snapshots_internal ...', 0, 1) WITH NOWAIT;
    ALTER TABLE core.snapshots_internal
        ADD CONSTRAINT [FK_snapshots_source_info] FOREIGN KEY(source_id)
        REFERENCES core.source_info_internal (source_id)
END;
GO

-- snapshots_internal.FK_snapshots_snapshots_timetable
IF OBJECT_ID ('core.FK_snapshots_snapshots_timetable', 'F') IS NULL
BEGIN
    RAISERROR ('Creating foreign key [FK_snapshots_snapshots_timetable] on core.snapshots_internal ...', 0, 1) WITH NOWAIT;
    ALTER TABLE core.snapshots_internal
        ADD CONSTRAINT [FK_snapshots_snapshots_timetable] FOREIGN KEY(snapshot_time_id)
        REFERENCES core.snapshot_timetable_internal (snapshot_time_id)
END;
GO


-- SNAPSHOTS VIEW
--
IF (NOT OBJECT_ID(N'core.snapshots', 'V') IS NULL)
BEGIN
    PRINT 'Dropping view [core].[snapshots]...'
    DROP VIEW core.snapshots
END
GO

PRINT 'Creating view [core].[snapshots]...'
GO
CREATE VIEW core.snapshots
AS 
    SELECT 
        s.source_id,
        s.snapshot_id, 
        s.snapshot_time_id,
        t.snapshot_time, 
        CASE src.days_until_expiration
            WHEN 0 THEN NULL
            ELSE DATEADD(DAY, src.days_until_expiration, t.snapshot_time)
        END AS valid_through,
        src.instance_name, 
        src.collection_set_uid, 
        src.operator,
        s.log_id
    FROM core.source_info_internal src, core.snapshots_internal s, core.snapshot_timetable_internal t
    WHERE src.source_id = s.source_id AND s.snapshot_time_id = t.snapshot_time_id
GO

--
-- This stored proc creates a snapshot entry and return the id
--
IF (NOT OBJECT_ID(N'core.sp_create_snapshot', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [core].[sp_create_snapshot] ...'
    DROP PROCEDURE [core].[sp_create_snapshot]
END
GO 

PRINT 'Creating procedure [core].[sp_create_snapshot] ...'
GO
CREATE PROCEDURE core.sp_create_snapshot
    @collection_set_uid     uniqueidentifier,
    @collector_type_uid     uniqueidentifier,
    @machine_name           sysname,
    @named_instance         sysname,
    @log_id                 bigint,
    @snapshot_id            int OUTPUT
AS 
BEGIN
    SET NOCOUNT ON
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE

    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'mdw_writer'), 0) = 1) AND NOT (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1))
    BEGIN
        RAISERROR(14677, 16, -1, 'mdw_writer')
        RETURN(1) -- Failure
    END
    
    DECLARE @operator sysname;
    SELECT @operator = SUSER_SNAME();

    DECLARE @instance_name sysname
    SET @named_instance = NULLIF(RTRIM(LTRIM(@named_instance)), N'')
    IF (@named_instance = 'MSSQLSERVER')
        SET @instance_name = @machine_name
    ELSE
        SET @instance_name = @machine_name + N'\' + @named_instance

    -- Parameters check
    -- Find the source_id that matches the requested collection set and operator
    DECLARE @source_id  int
    SET @source_id = (SELECT source_id 
                        FROM core.source_info_internal
                        WHERE collection_set_uid = @collection_set_uid 
                          AND operator = @operator
                          AND instance_name = @instance_name)

    IF(@source_id IS NULL)
    BEGIN
        DECLARE @collection_set_uid_as_char NVARCHAR(36)
        SELECT @collection_set_uid_as_char = CONVERT(NVARCHAR(36), @collection_set_uid)
        RAISERROR(14679, -1, -1, N'@collection_set_uid', @collection_set_uid_as_char)
    END

    -- Make sure the collector_type is registered in this warehouse
    IF NOT EXISTS (SELECT collector_type_uid FROM core.supported_collector_types WHERE collector_type_uid = @collector_type_uid)
    BEGIN
        DECLARE @collector_type_uid_as_char NVARCHAR(36)
        SELECT @collector_type_uid_as_char = CONVERT(NVARCHAR(36), @collector_type_uid)
        RAISERROR(14679, -1, -1, N'@collector_type_uid', @collector_type_uid_as_char)
    END

    -- Get the snapshot time
    BEGIN TRY
        BEGIN TRAN
        DECLARE @snapshot_time_id int

        IF NOT EXISTS (SELECT snapshot_time_id FROM core.snapshot_timetable_internal WITH(UPDLOCK) WHERE snapshot_time > DATEADD (minute, -1, SYSDATETIMEOFFSET()))
        BEGIN
            INSERT INTO core.snapshot_timetable_internal
            (
                snapshot_time
            )
            VALUES
            (
                SYSDATETIMEOFFSET()
            )
            SET @snapshot_time_id = SCOPE_IDENTITY()
        END
        ELSE
        BEGIN
            SET @snapshot_time_id = (SELECT MAX(snapshot_time_id) FROM core.snapshot_timetable_internal)
        END

        -- Finally insert an entry into snapshots table
        INSERT INTO core.snapshots_internal
        (
            snapshot_time_id,
            source_id,
            log_id
        )
        VALUES
        (
            @snapshot_time_id,
            @source_id,
            @log_id
        )
        SET @snapshot_id = SCOPE_IDENTITY()

        IF (@snapshot_id IS NULL)
        BEGIN
            RAISERROR(14262, -1, -1, '@snapshot_id', @snapshot_id)
            RETURN(1)
        END
        ELSE
        BEGIN
            COMMIT TRAN
        END
    END TRY
    BEGIN CATCH
        IF (@@TRANCOUNT > 0) 
            ROLLBACK TRANSACTION

        -- Rethrow the error
        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorSeverity  INT;
        DECLARE @ErrorState     INT;
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        DECLARE @ErrorProcedure NVARCHAR(200);
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorSeverity = ERROR_SEVERITY(),
               @ErrorState = ERROR_STATE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE(),
               @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');

        RAISERROR (14684, -1, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);
    END CATCH

END
GO

--
-- This stored proc updates data in source_info table 
--
IF (NOT OBJECT_ID(N'core.sp_update_data_source', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [core].[sp_update_data_source] ...'
    DROP PROCEDURE [core].[sp_update_data_source]
END
GO 

PRINT 'Creating procedure [core].[sp_update_data_source] ...'
GO
CREATE PROCEDURE [core].[sp_update_data_source]
    @collection_set_uid     uniqueidentifier,
    @machine_name           sysname,
    @named_instance         sysname,
    @days_until_expiration  smallint,
    @source_id              int OUTPUT
AS
BEGIN
    SET NOCOUNT ON
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE

    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'mdw_writer'), 0) = 1) AND NOT (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1))
    BEGIN
        RAISERROR(14677, 16, -1, 'mdw_writer')
        RETURN(1) -- Failure
    END

    -- Parameters check
    IF (@collection_set_uid IS NULL)
    BEGIN
        RAISERROR(14200, -1, -1, '@collection_set_uid')
        RETURN(1) -- Failure
    END

    SET @machine_name = NULLIF(RTRIM(LTRIM(@machine_name)), N'')
    IF (@machine_name IS NULL)
    BEGIN
        RAISERROR(14200, -1, -1, '@machine_name')
        RETURN(1) -- Failure
    END

    DECLARE @instance_name sysname
    SET @named_instance = NULLIF(RTRIM(LTRIM(@named_instance)), N'')
    IF (@named_instance = 'MSSQLSERVER')
        SET @instance_name = @machine_name
    ELSE
        SET @instance_name = @machine_name + N'\' + @named_instance

    IF (@days_until_expiration IS NULL)
    BEGIN
        RAISERROR(14200, -1, -1, '@days_until_expiration')
        RETURN(1) -- Failure
    END

    IF (@days_until_expiration < 0)
    BEGIN
        RAISERROR(14266, -1, -1, '@days_until_expiration', ' >= 0')
        RETURN (1) -- Failure
    END
    
    DECLARE @operator sysname
    SELECT @operator = SUSER_SNAME()

    BEGIN TRY
        BEGIN TRAN
        
        -- Insert data into the table
        -- We specify the lock hint, in order to keep the exlusive lock on the row from the moment we select it till we 
        -- update it
        SET @source_id = (SELECT source_id FROM core.source_info_internal WITH(UPDLOCK) WHERE collection_set_uid = @collection_set_uid AND instance_name = @instance_name AND operator = @operator)
        IF @source_id IS NULL
        BEGIN
            INSERT INTO core.source_info_internal
            (
                collection_set_uid,
                instance_name,
                days_until_expiration,
                operator
            )
            VALUES
            (
                @collection_set_uid,
                @instance_name,
                @days_until_expiration,
                @operator
            )
            SET @source_id = SCOPE_IDENTITY()
        END
        ELSE
        BEGIN
            UPDATE core.source_info_internal
            SET 
                days_until_expiration = @days_until_expiration
            WHERE source_id = @source_id;
        END
            
        COMMIT TRAN
        RETURN (0)
    END TRY
    BEGIN CATCH
        IF (@@TRANCOUNT > 0) 
            ROLLBACK TRANSACTION

        -- Rethrow the error
        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorSeverity  INT;
        DECLARE @ErrorState     INT;
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        DECLARE @ErrorProcedure NVARCHAR(200);
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorSeverity = ERROR_SEVERITY(),
               @ErrorState = ERROR_STATE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE(),
               @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');

        RAISERROR (14684, -1, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);
        RETURN (1)
    END CATCH
END
GO


--
-- This stored proc adds new entry in supported_collector_types table
--
IF (NOT OBJECT_ID(N'core.sp_add_collector_type', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [core].[sp_add_collector_type] ...'
    DROP PROCEDURE [core].[sp_add_collector_type]
END
GO 

PRINT 'Creating procedure [core].[sp_add_collector_type] ...'
GO
CREATE PROCEDURE [core].[sp_add_collector_type]
    @collector_type_uid     uniqueidentifier
AS
BEGIN
    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'mdw_admin'), 0) = 1) AND NOT (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1))
    BEGIN
        RAISERROR(14677, 16, -1, 'mdw_admin')
        RETURN(1) -- Failure
    END

    -- Parameters check
    IF (@collector_type_uid IS NULL)
    BEGIN
        RAISERROR(14200, -1, -1, '@collector_type_uid')
        RETURN(1) -- Failure
    END

    -- Insert new collector type
    IF NOT EXISTS (SELECT collector_type_uid FROM core.supported_collector_types WHERE collector_type_uid = @collector_type_uid)
    BEGIN
        INSERT INTO core.supported_collector_types
        (
            collector_type_uid
        )
        VALUES
        (
            @collector_type_uid
        )
    END
    ELSE
    BEGIN
        -- Raise an info message, but do not fail
        DECLARE @collector_type_uid_as_char NVARCHAR(36)
        SELECT @collector_type_uid_as_char = CONVERT(NVARCHAR(36), @collector_type_uid)
        RAISERROR(14261, 10, -1, '@collector_type_uid', @collector_type_uid_as_char)
    END

    RETURN (0)
END
GO

--
-- This stored proc removes and existing entry from supported_collector_types table
--
IF (NOT OBJECT_ID(N'core.sp_remove_collector_type', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [core].[sp_remove_collector_type] ...'
    DROP PROCEDURE [core].[sp_remove_collector_type]
END
GO 

PRINT 'Creating procedure [core].[sp_remove_collector_type] ...'
GO
CREATE PROCEDURE [core].[sp_remove_collector_type]
    @collector_type_uid     uniqueidentifier
AS
BEGIN
    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'mdw_admin'), 0) = 1) AND NOT (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1))
    BEGIN
        RAISERROR(14677, 16, -1, 'mdw_admin')
        RETURN(1) -- Failure
    END

    -- Parameters check
    IF (@collector_type_uid IS NULL)
    BEGIN
        RAISERROR(14200, -1, -1, '@collector_type_uid')
        RETURN(1) -- Failure
    END

    -- Delete collector type
    IF EXISTS (SELECT collector_type_uid FROM core.supported_collector_types WHERE collector_type_uid = @collector_type_uid)
    BEGIN
        DELETE FROM core.supported_collector_types WHERE collector_type_uid = @collector_type_uid
    END
    ELSE
    BEGIN
        DECLARE @collector_type_uid_as_char NVARCHAR(36)
        SELECT @collector_type_uid_as_char = CONVERT(NVARCHAR(36), @collector_type_uid)
        RAISERROR(14262, -1, -1, '@collector_type_uid', @collector_type_uid_as_char)
        RETURN (1) -- Failure
    END

    RETURN (0)
END
GO

--
-- This table is used to determine status of current purge operation.
-- Contains records only if a sp_stop_purge operation was requested.
--
PRINT ''
GO
IF (OBJECT_ID(N'[core].[purge_info_internal]', 'U') IS NULL)
BEGIN
    PRINT 'Creating table [core].[purge_info_internal]...'
    CREATE TABLE [core].[purge_info_internal] (
      stop_purge bit NOT NULL,
    ) ON [PRIMARY]

END
GO

--
-- This stored proc removes data from the warehouse that reached its expiration date
--
IF (NOT OBJECT_ID(N'core.sp_purge_data', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [core].[sp_purge_data] ...'
    DROP PROCEDURE [core].[sp_purge_data]
END
GO 

PRINT 'Creating procedure [core].[sp_purge_data] ...'
GO
CREATE PROCEDURE [core].[sp_purge_data]
	@retention_days smallint = NULL,
	@instance_name sysname = NULL,
	@collection_set_uid uniqueidentifier = NULL,
	@duration smallint = NULL
AS
BEGIN
    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'mdw_admin'), 0) = 1) AND NOT (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1))
    BEGIN
        RAISERROR(14677, 16, -1, 'mdw_admin')
        RETURN(1) -- Failure
    END

    -- Validate parameters
    IF ((@retention_days IS NOT NULL) AND (@retention_days < 0))
    BEGIN
        RAISERROR(14200, -1, -1, '@retention_days')
        RETURN(1) -- Failure
    END

    IF ((@duration IS NOT NULL) AND (@duration < 0))
    BEGIN
        RAISERROR(14200, -1, -1, '@duration')
        RETURN(1) -- Failure
    END

	-- This table will contain a record if somebody requests purge to stop
	-- If user requested us to purge data - we reset the content of it - and proceed with purge
	-- If somebody in a different session wants purge operations to stop he adds a record
	-- that we will discover while purge in progress
	--
	-- We dont clear this flag when we exit since multiple purge operations with differnet
	-- filters may proceed, and we want all of them to stop.
	DELETE FROM [core].[purge_info_internal]

	SET @instance_name = NULLIF(LTRIM(RTRIM(@instance_name)), N'')

	-- Calculate the time when the operation should stop (NULL otherwise)
	DECLARE @end_time datetime
	IF (@duration IS NOT NULL)
	BEGIN
		SET @end_time = DATEADD(minute, @duration, GETUTCDATE())
	END

	-- Declare table that will be used to find what are the valid
	-- candidate snapshots that could be selected for purge
	DECLARE @purge_candidates table
	(
		snapshot_id int NOT NULL,
		snapshot_time datetime NOT NULL,
		instance_name sysname NOT NULL,
		collection_set_uid uniqueidentifier NOT NULL
	)

	-- Find candidates that match the retention_days criteria (if specified)
	IF (@retention_days IS NULL)
	BEGIN
		-- User did not specified a value for @retention_days, therfore we
		-- will use the default expiration day as marked in the source info
		INSERT INTO @purge_candidates
		SELECT s.snapshot_id, s.snapshot_time, s.instance_name, s.collection_set_uid
		FROM core.snapshots s
		WHERE (GETUTCDATE() >= s.valid_through)
	END
	ELSE
	BEGIN
		-- User specified a value for @retention_days, we will use this overriden value
		-- when deciding what means old enough to qualify for purge this overrides
		-- the days_until_expiration value specified in the source_info_internal table
		INSERT INTO @purge_candidates
		SELECT s.snapshot_id, s.snapshot_time, s.instance_name, s.collection_set_uid
		FROM core.snapshots s
		WHERE GETUTCDATE() >= DATEADD(DAY, @retention_days, s.snapshot_time)
	END

	-- Determine which is the oldest snapshot, from the list of candidates
	DECLARE oldest_snapshot_cursor CURSOR FORWARD_ONLY READ_ONLY FOR
	SELECT p.snapshot_id, p.instance_name, p.collection_set_uid
	FROM @purge_candidates p
	WHERE 
		((@instance_name IS NULL) or (p.instance_name = @instance_name)) AND
		((@collection_set_uid IS NULL) or (p.collection_set_uid = @collection_set_uid))
	ORDER BY p.snapshot_time ASC	

	OPEN oldest_snapshot_cursor

	DECLARE @stop_purge int
	DECLARE @oldest_snapshot_id int
	DECLARE @oldest_instance_name sysname
	DECLARE @oldest_collection_set_uid uniqueidentifier

	FETCH NEXT FROM oldest_snapshot_cursor
	INTO @oldest_snapshot_id, @oldest_instance_name, @oldest_collection_set_uid

	-- As long as there are snapshots that matched the time criteria
	WHILE @@FETCH_STATUS = 0
	BEGIN

		-- Filter out records that do not match the other filter crieria
		IF ((@instance_name IS NULL) or (@oldest_instance_name = @instance_name))
		BEGIN

			-- There was no filter specified for instance_name or the instance matches the filter
			IF ((@collection_set_uid IS NULL) or (@oldest_collection_set_uid = @collection_set_uid))
			BEGIN

				-- There was no filter specified for the collection_set_uid or the collection_set_uid matches the filter
				BEGIN TRANSACTION tran_sp_purge_data

				-- Purge data associated with this snapshot. Note: deleting this snapshot
				-- triggers cascade delete in all warehouse tables based on the foreign key 
				-- relationship to snapshots table

				-- Cascade cleanup of all data related referencing oldest snapshot
				DELETE core.snapshots_internal
				FROM core.snapshots_internal s
				WHERE s.snapshot_id = @oldest_snapshot_id

				COMMIT TRANSACTION tran_sp_purge_data

				PRINT 'Snapshot #' + CONVERT(NVARCHAR(MAX),@oldest_snapshot_id) + ' purged.';
			END

		END

		-- Check if the execution of the stored proc exceeded the @duration specified
		IF (@duration IS NOT NULL)
		BEGIN
			IF (GETUTCDATE()>=@end_time)
			BEGIN
				PRINT 'Stopping purge. More than ' + CONVERT(NVARCHAR(MAX),@duration) + ' minutes passed since the start of operation.';
				BREAK
			END
		END

		-- Check if somebody wanted to stop the purge operation
		SELECT @stop_purge = COUNT(stop_purge) FROM [core].[purge_info_internal]
		IF (@stop_purge > 0)
		BEGIN
				PRINT 'Stopping purge. Detected a user request to stop purge.';
			BREAK
		END

		-- Move to next oldest snapshot
		FETCH NEXT FROM oldest_snapshot_cursor
		INTO @oldest_snapshot_id, @oldest_instance_name, @oldest_collection_set_uid

	END

	CLOSE oldest_snapshot_cursor
	DEALLOCATE oldest_snapshot_cursor

END
GO


--
-- This stored procedure is used to stop all purge operations in progress
--
IF (NOT OBJECT_ID(N'core.sp_stop_purge', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [core].[sp_stop_purge] ...'
    DROP PROCEDURE [core].[sp_stop_purge]
END
GO 

PRINT 'Creating procedure [core].[sp_stop_purge] ...'
GO
CREATE PROCEDURE [core].[sp_stop_purge]
AS
BEGIN
	INSERT INTO [core].[purge_info_internal] (stop_purge) VALUES (1)
END
GO


--
-- This function verifies if the caller matches the operator set for the snapshot 
--
IF (OBJECT_ID(N'core.fn_check_operator', 'FN') IS NULL)
BEGIN
    PRINT 'Creating function [core].[fn_check_operator] ...'
    DECLARE @sql nvarchar(max)
    SET @sql = 
    'CREATE FUNCTION [core].[fn_check_operator](
        @snapshot_id int
    )
    RETURNS bit
    AS
    BEGIN
        DECLARE @retval bit;
        
        DECLARE @operator sysname;
        SELECT @operator=operator FROM core.snapshots WHERE snapshot_id = @snapshot_id;
        IF (@operator = SUSER_SNAME())
            SELECT @retval = 1;
        ELSE
            SELECT @retval = 0;
        RETURN @retval;
    END;'

    EXEC sp_executesql @sql;
END
GO

-- Table wait_categories
--
-- This table contains CPU and wait categories.
--
IF (NOT OBJECT_ID(N'core.wait_types', 'U') IS NULL)
BEGIN
    -- Must drop wait_types first since it references wait_categories via FK
    PRINT 'Dropping table [core].[wait_types] ...'
    DROP TABLE [core].[wait_types]
END

IF (NOT OBJECT_ID(N'core.wait_categories', 'U') IS NULL)
BEGIN
    PRINT 'Dropping table [core].[wait_categories] ...'
    DROP TABLE [core].[wait_categories]
END

PRINT 'Creating table [core].[wait_categories] ...'

CREATE TABLE [core].[wait_categories](
	    [category_id] [smallint] NOT NULL,
	    [category_name] [nvarchar](20) NOT NULL,
	    [ignore] [bit] NOT NULL, 
     CONSTRAINT [PK_categories] PRIMARY KEY CLUSTERED ([category_id] ASC)WITH (IGNORE_DUP_KEY = OFF) ON [PRIMARY]

) ON [PRIMARY]
GO

-- Table wait_types
--
-- This table contains relation between cpu/wait events and cpu/wait categories for each version of SQL Server.
--
--
PRINT 'Creating table [core].[wait_types] ...'

CREATE TABLE [core].[wait_types](
	    [category_id] [smallint] NOT NULL,
	    [wait_type] [nvarchar](45) NOT NULL,
     CONSTRAINT [PK_events] PRIMARY KEY CLUSTERED ([wait_type] ASC) WITH (IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]

ALTER TABLE [core].[wait_types] ADD CONSTRAINT [FK_events_categories] FOREIGN KEY([category_id])
REFERENCES [core].[wait_categories] ([category_id])
GO


IF (NOT OBJECT_ID(N'core.wait_types_categorized', 'V') IS NULL)
BEGIN
    PRINT 'Dropping view [core].[wait_types_categorized] ...'
    DROP VIEW [core].[wait_types_categorized];
END;
GO

PRINT 'Creating view [core].[wait_types_categorized] ...'
GO
CREATE VIEW [core].[wait_types_categorized]
AS
    SELECT 
        ct.category_name,
        ev.wait_type, 
        ct.category_id, 
        ct.ignore
    FROM core.wait_categories ct
    INNER JOIN core.wait_types ev ON (ev.category_id = ct.category_id)
GO

-- Insert Categories
--
SET NOCOUNT ON;
INSERT INTO [core].[wait_categories] ([category_id], [category_name], [ignore]) VALUES (0, N'CPU', 0);
INSERT INTO [core].[wait_categories] ([category_id], [category_name], [ignore]) VALUES (1, N'Backup', 0);
INSERT INTO [core].[wait_categories] ([category_id], [category_name], [ignore]) VALUES (2, N'SQLCLR', 0);
INSERT INTO [core].[wait_categories] ([category_id], [category_name], [ignore]) VALUES (3, N'Parallelism', 1);
INSERT INTO [core].[wait_categories] ([category_id], [category_name], [ignore]) VALUES (4, N'Latch', 0);
INSERT INTO [core].[wait_categories] ([category_id], [category_name], [ignore]) VALUES (5, N'Lock', 0);
INSERT INTO [core].[wait_categories] ([category_id], [category_name], [ignore]) VALUES (6, N'Network I/O', 0);
INSERT INTO [core].[wait_categories] ([category_id], [category_name], [ignore]) VALUES (7, N'Buffer I/O', 0);
INSERT INTO [core].[wait_categories] ([category_id], [category_name], [ignore]) VALUES (8, N'Buffer Latch', 0);
INSERT INTO [core].[wait_categories] ([category_id], [category_name], [ignore]) VALUES (9, N'Memory', 0);
INSERT INTO [core].[wait_categories] ([category_id], [category_name], [ignore]) VALUES (10, N'Logging', 0);
INSERT INTO [core].[wait_categories] ([category_id], [category_name], [ignore]) VALUES (11, N'Compilation', 0);
INSERT INTO [core].[wait_categories] ([category_id], [category_name], [ignore]) VALUES (12, N'Transaction', 0);
INSERT INTO [core].[wait_categories] ([category_id], [category_name], [ignore]) VALUES (13, N'Idle', 1);
INSERT INTO [core].[wait_categories] ([category_id], [category_name], [ignore]) VALUES (14, N'User Waits', 1);
INSERT INTO [core].[wait_categories] ([category_id], [category_name], [ignore]) VALUES (15, N'Other', 0);
INSERT INTO [core].[wait_categories] ([category_id], [category_name], [ignore]) VALUES (16, N'Full Text Search', 0);

-- Insert Events
--
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (0, N'CPU');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'MISCELLANEOUS');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (5, N'LCK_M_SCH_S');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (5, N'LCK_M_SCH_M');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (5, N'LCK_M_S');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (5, N'LCK_M_U');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (5, N'LCK_M_X');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (5, N'LCK_M_IS');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (5, N'LCK_M_IU');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (5, N'LCK_M_IX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (5, N'LCK_M_SIU');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (5, N'LCK_M_SIX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (5, N'LCK_M_UIX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (5, N'LCK_M_BU');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (5, N'LCK_M_RS_S');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (5, N'LCK_M_RS_U');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (5, N'LCK_M_RIn_NL');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (5, N'LCK_M_RIn_S');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (5, N'LCK_M_RIn_U');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (5, N'LCK_M_RIn_X');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (5, N'LCK_M_RX_S');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (5, N'LCK_M_RX_U');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (5, N'LCK_M_RX_X');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (4, N'LATCH_NL');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (4, N'LATCH_KP');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (4, N'LATCH_SH');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (4, N'LATCH_UP');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (4, N'LATCH_EX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (4, N'LATCH_DT');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (8, N'PAGELATCH_NL');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (8, N'PAGELATCH_KP');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (8, N'PAGELATCH_SH');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (8, N'PAGELATCH_UP');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (8, N'PAGELATCH_EX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (8, N'PAGELATCH_DT');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (7, N'PAGEIOLATCH_NL');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (7, N'PAGEIOLATCH_KP');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (7, N'PAGEIOLATCH_SH');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (7, N'PAGEIOLATCH_UP');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (7, N'PAGEIOLATCH_EX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (7, N'PAGEIOLATCH_DT');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (12, N'TRAN_MARKLATCH_NL');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (12, N'TRAN_MARKLATCH_KP');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (12, N'TRAN_MARKLATCH_SH');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (12, N'TRAN_MARKLATCH_UP');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (12, N'TRAN_MARKLATCH_EX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (12, N'TRAN_MARKLATCH_DT');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'LAZYWRITER_SLEEP');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (7, N'IO_COMPLETION');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (7, N'ASYNC_IO_COMPLETION');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (6, N'ASYNC_NETWORK_IO');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'SLEEP_BPOOL_FLUSH');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'CHKPT');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'SLEEP_TASK');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'SLEEP_SYSTEMTASK');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (9, N'RESOURCE_SEMAPHORE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (12, N'DTC');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (6, N'OLEDB');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'FAILPOINT');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'RESOURCE_QUEUE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (7, N'ASYNC_DISKPOOL_LOCK');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'THREADPOOL');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'DEBUG');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (7, N'REPLICA_WRITES');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'BROKER_RECEIVE_WAITFOR');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'DBMIRRORING_CMD');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'WAIT_FOR_RESULTS');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (0, N'SOS_SCHEDULER_YIELD');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (9, N'SOS_VIRTUALMEMORY_LOW');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (9, N'SOS_RESERVEDMEMBLOCKLIST');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SOS_LOCALALLOCATORLIST');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SOS_CALLBACK_REMOVAL');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (9, N'LOWFAIL_MEMMGR_QUEUE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (1, N'BACKUP');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (1, N'BACKUPBUFFER');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (1, N'BACKUPIO');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (1, N'BACKUPTHREAD');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'DBMIRROR_DBM_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'DBMIRROR_DBM_EVENT');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (6, N'DBMIRROR_SEND');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'CURSOR_ASYNC');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'HTTP_ENUMERATION');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (16, N'SOAP_READ');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (16, N'SOAP_WRITE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'DUMP_LOG_COORDINATOR');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (1, N'DISKIO_SUSPEND');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'IMPPROV_IOWAIT');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'QNMANAGER_ACQUIRE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'DEADLOCK_TASK_SEARCH');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'REPL_SCHEMA_ACCESS');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'REPL_CACHE_ACCESS');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SQLSORT_SORTMUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SQLSORT_NORMMUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SQLTRACE_WAIT_ENTRIES');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SQLTRACE_LOCK');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'SQLTRACE_BUFFER_FLUSH');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SQLTRACE_SHUTDOWN');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'MSQL_SYNC_PIPE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'QUERY_TRACEOUT');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (6, N'DTC_STATE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (7, N'FCB_REPLICA_WRITE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (7, N'FCB_REPLICA_READ');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (10, N'WRITELOG');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'HTTP_ENDPOINT_COLLCREATE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (3, N'EXCHANGE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'DBTABLE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'EC');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'TEMPOBJ');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (12, N'XACTLOCKINFO');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (10, N'LOGMGR');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (9, N'CMEMTHREAD');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (3, N'CXPACKET');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (14, N'WAITFOR');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'CURSOR');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (3, N'EXECSYNC');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SOSHOST_INTERNAL');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SOSHOST_SLEEP');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SOSHOST_WAITFORDONE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SOSHOST_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SOSHOST_EVENT');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SOSHOST_SEMAPHORE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SOSHOST_RWLOCK');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SOSHOST_TRACELOCK');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'MSQL_XP');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (6, N'MSQL_DQ');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (10, N'LOGBUFFER');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (12, N'TRANSACTION_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (16, N'MSSEARCH');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (12, N'XACTWORKSPACE_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (2, N'CLR_JOIN');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (2, N'CLR_CRST');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (2, N'CLR_SEMAPHORE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'CLR_MANUAL_EVENT');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'CLR_AUTO_EVENT');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (2, N'CLR_MONITOR');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (2, N'CLR_RWLOCK_READER');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (2, N'CLR_RWLOCK_WRITER');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (2, N'SQLCLR_QUANTUM_PUNISHMENT');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (2, N'SQLCLR_APPDOMAIN');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (2, N'SQLCLR_ASSEMBLY');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'KTM_ENLISTMENT');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'KTM_RECOVERY_RESOLUTION');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'KTM_RECOVERY_MANAGER');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (2, N'SQLCLR_DEADLOCK_DETECTION');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'QPJOB_WAITFOR_ABORT');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'QPJOB_KILL');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'BAD_PAGE_PROCESS');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (1, N'BACKUP_OPERATOR');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'PRINT_ROLLBACK_PROGRESS');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'ENABLE_VERSIONING');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'DISABLE_VERSIONING');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'REQUEST_DISPENSER_PAUSE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'DROPTEMP');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'FT_RESTART_CRAWL');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'FT_RESUME_CRAWL');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (10, N'LOGMGR_RESERVE_APPEND');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (10, N'LOGMGR_FLUSH');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (12, N'XACT_OWN_TRANSACTION');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (12, N'XACT_RECLAIM_SESSION');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (12, N'DTC_WAITFOR_OUTCOME');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (12, N'DTC_RESOLVE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SEC_DROP_TEMP_KEY');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SRVPROC_SHUTDOWN');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (6, N'NET_WAITFOR_PACKET');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (12, N'DTC_ABORT_REQUEST');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (12, N'DTC_TMDOWN_REQUEST');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'RECOVER_CHANGEDB');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'WORKTBL_DROP');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'MIRROR_SEND_MESSAGE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'SNI_HTTP_ACCEPT');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SNI_HTTP_WAITFOR_0_DISCON');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (9, N'UTIL_PAGE_ALLOC');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'SERVER_IDLE_CHECK');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (1, N'BACKUP_CLIENTLOCK');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (4, N'DEADLOCK_ENUM_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (4, N'INDEX_USAGE_STATS_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (4, N'VIEW_DEFINITION_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'QUERY_NOTIFICATION_MGR_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'QUERY_NOTIFICATION_TABLE_MGR_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'QUERY_NOTIFICATION_SUBSCRIPTION_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'QUERY_NOTIFICATION_UNITTEST_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'IMP_IMPORT_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (11, N'RESOURCE_SEMAPHORE_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'IO_AUDIT_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'BUILTIN_HASHKEY_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SOS_PROCESS_AFFINITY_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (12, N'MSQL_XACT_MGR_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (12, N'MSQL_XACT_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'QRY_MEM_GRANT_INFO_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SOS_STACKSTORE_INIT_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SOS_SYNC_TASK_ENQUEUE_EVENT');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SOS_OBJECT_STORE_DESTROY_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'EE_PMOLOCK');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (11, N'RESOURCE_SEMAPHORE_QUERY_COMPILE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (11, N'RESOURCE_SEMAPHORE_SMALL_QUERY');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'ABR');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (2, N'ASSEMBLY_LOAD');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'BROKER_CONNECTION_RECEIVE_TASK');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'BROKER_ENDPOINT_STATE_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'BROKER_EVENTHANDLER');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'BROKER_INIT');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'BROKER_MASTERSTART');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'BROKER_REGISTERALLENDPOINTS');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'BROKER_SHUTDOWN');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'BROKER_TASK_STOP');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'BROKER_TRANSMITTER');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'CHECK_PRINT_RECORD');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'CHECKPOINT_QUEUE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (2, N'CLR_MEMORY_SPY');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (2, N'CLR_TASK_START');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (2, N'CLRHOST_STATE_ACCESS');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'DAC_INIT');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'DBCC_COLUMN_TRANSLATION_CACHE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'DBMIRROR_EVENTS_QUEUE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'DBMIRROR_WORKER_QUEUE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'DLL_LOADING_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'DUMP_LOG_COORDINATOR_QUEUE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'DUMPTRIGGER');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'EE_SPECPROC_MAP_INIT');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'ERROR_REPORTING_MANAGER');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'EXECUTION_PIPE_EVENT_INTERNAL');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (2, N'FS_GARBAGE_COLLECTOR_SHUTDOWN');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'FSAGENT');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'FULLTEXT GATHERER');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'GUARDIAN');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'HTTP_START');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'INTERNAL_TESTING');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'KSOURCE_WAKEUP');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'LOGMGR_QUEUE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'ONDEMAND_TASK_QUEUE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'PARALLEL_BACKUP_QUEUE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'QUERY_ERRHDL_SERVICE_DONE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'QUERY_EXECUTION_INDEX_SORT_EVENT_OPEN');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'QUERY_OPTIMIZER_PRINT_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'QUERY_REMOTE_BRICKS_DONE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'REQUEST_FOR_DEADLOCK_SEARCH');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SEQUENTIAL_GUID');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SHUTDOWN');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'SLEEP_DBSTARTUP');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'SLEEP_DCOMSTARTUP');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'SLEEP_MSDBSTARTUP');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'SLEEP_TEMPDBSTARTUP');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SNI_CRITICAL_SECTION');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SNI_LISTENER_ACCESS');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SNI_TASK_COMPLETION');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SOS_DISPATCHER_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'TIMEPRIV_TIMEPERIOD');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'TRACEWRITE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'VIA_ACCEPT');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'WAITFOR_TASKSHUTDOWN');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'WAITSTAT_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'WCC');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'XE_TIMER_EVENT');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'XE_DISPATCHER_WAIT');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'XE_TIMER_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'XE_TIMER_TASK_DONE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'XE_BUFFERMGR_ALLPROCECESSED_EVENT');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'XE_BUFFERMGR_FREEBUF_EVENT');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'XE_DISPATCHER_JOIN');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'XE_MODULEMGR_SYNC');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'XE_OLS_LOCK');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'XE_SERVICES_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'XE_SESSION_CREATE_SYNC');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'XE_SESSION_SYNC');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'XE_STM_CREATE')
GO

IF (NOT OBJECT_ID(N'core.fn_query_text_from_handle', 'TF') IS NULL)
BEGIN
    PRINT 'Dropping function [core].[fn_query_text_from_handle] ...'
    DROP FUNCTION [core].[fn_query_text_from_handle]
END
GO 

PRINT 'Creating function [core].[fn_query_text_from_handle] ...'
GO
CREATE FUNCTION [core].[fn_query_text_from_handle](
    @handle varbinary(64), 
    @statement_start_offset int, 
    @statement_end_offset int)
RETURNS @query_text TABLE (database_id smallint, object_id int, encrypted bit, query_text nvarchar(max))
BEGIN
      IF @handle IS NOT NULL
      BEGIN
            DECLARE @start int, @end int
            DECLARE @dbid smallint, @objectid int, @encrypted bit
            DECLARE @batch nvarchar(max), @query nvarchar(max)

            -- statement_end_offset is zero prior to beginning query execution (e.g., compilation)
            SELECT 
                  @start = ISNULL(@statement_start_offset, 0), 
                  @end = CASE WHEN @statement_end_offset is null or @statement_end_offset = 0 THEN -1
                                    ELSE @statement_end_offset 
                              END
 
            SELECT @dbid = t.dbid, 
                  @objectid = t.objectid, 
                  @encrypted = t.encrypted, 
                  @batch = t.text 
            FROM sys.dm_exec_sql_text(@handle) AS t

            SELECT @query = CASE 
                        WHEN @encrypted = CAST(1 as bit) THEN N'encrypted text' 
                        ELSE LTRIM(SUBSTRING(@batch, @start / 2 + 1, ((CASE WHEN @end = -1 THEN DATALENGTH(@batch) 
                                          ELSE @end END) - @start) / 2))
                  end

            -- Found internal queries (e.g., CREATE INDEX) with end offset of original batch that is 
            -- greater than the length of the internal query and thus returns nothing if we don''t do this
            IF DATALENGTH(@query) = 0
            BEGIN
                  SELECT @query = @batch
            END

            INSERT INTO @query_text (database_id, object_id, encrypted, query_text) 
            VALUES (@dbid, @objectid, @encrypted, @query)
      END
      RETURN
END
GO


-- Table performance_counter_report_groups
--
-- This table lists the counters that are used by each report.  Referenced in [snapshots].[rpt_generic_perfmon]. 
--
IF (NOT OBJECT_ID(N'[core].[performance_counter_report_group_items]', 'U') IS NULL)
BEGIN
    -- Must drop wait_types first since it references wait_categories via FK
    PRINT 'Dropping table [core].[performance_counter_report_group_items] ...'
    DROP TABLE [core].[performance_counter_report_group_items]
END
GO

PRINT 'Creating table [core].[performance_counter_report_group_items] ...'
CREATE TABLE [core].[performance_counter_report_group_items](
        [counter_group_item_id] int IDENTITY NOT NULL PRIMARY KEY CLUSTERED, 
	    [counter_group_id] nvarchar(128) NOT NULL,          -- e.g. report name
	    [counter_subgroup_id] nvarchar(128) NOT NULL,       -- e.g. chart name, used by the chart to filter out its rows from the larger resultset
        [series_name] nvarchar(512) NOT NULL,               -- data series label used for output
        [object_name] nvarchar(2048) NOT NULL,              -- perfmon object name
        [object_name_wildcards] bit NOT NULL,               -- 1 if wildcard expansion is needed for the perfmon object name (e.g. '%SQL%:General Statistics'), 0 otherwise
        [counter_name] nvarchar(2048) NOT NULL,             -- perfmon counter name
        [instance_name] nvarchar(2048) NULL,                -- perfmon instance name(s) to include (evaluated with LIKE)
        [not_instance_name] nvarchar(2048) NULL,            -- perfmon instance name(s) to omit (evaluated with LIKE, use NULL to skip this criteria)
        [multiply_by] numeric(28,10) NOT NULL,              -- value to multiple the counter  by on output (for unit conversion) 
        [divide_by_cpu_count] bit NOT NULL                  -- 1 if counter should be divided by the machine's CPU count (e.g. "Process(abc)\% Processor Time", 0 otherwise
) 
GO

-- Insert perfmon counter list for each report group 
--
DECLARE @CONVERT_BYTES_TO_MB numeric(28,10)
DECLARE @CONVERT_KB_TO_MB numeric(28,10)
SET @CONVERT_BYTES_TO_MB = 1.0 / (1024*1024)
SET @CONVERT_KB_TO_MB = 1.0 / (1024);

INSERT INTO [core].[performance_counter_report_group_items] ([counter_group_id],[counter_subgroup_id],[series_name],
    [object_name],[object_name_wildcards],[counter_name],[instance_name],[not_instance_name],[multiply_by],[divide_by_cpu_count]) 
VALUES 
    ('ServerActivity', 'memoryUsage', 'System', 'Process', 0, 'Working Set', '_Total', NULL, @CONVERT_BYTES_TO_MB, 0 ), 
    ('ServerActivity', 'memoryUsage', 'SQL Server', 'Process', 0, 'Working Set', '$(TARGETPROCESS)', NULL, @CONVERT_BYTES_TO_MB, 0 ), 
    ('ServerActivity', 'IOUsage', 'System', 'Process', 0, 'IO Read Bytes/sec', '_Total', NULL, @CONVERT_BYTES_TO_MB, 0 ), 
    ('ServerActivity', 'IOUsage', 'SQL Server', 'Process', 0, 'IO Read Bytes/sec', '$(TARGETPROCESS)', NULL, @CONVERT_BYTES_TO_MB, 0 ), 
    ('ServerActivity', 'cpuUsage', 'System', 'Processor', 0, '% Processor Time', '_Total', NULL, 1.0, 0 ), 
    ('ServerActivity', 'cpuUsage', 'SQL Server', 'Process', 0, '% Processor Time', '$(TARGETPROCESS)', NULL, 1.0, 1 ), 
    ('ServerActivity', 'networkUsage', 'System', 'Network Interface', 0, 'Bytes Total/sec', '%', NULL, 1.0, 0 ), 
    ('ServerActivity', 'sqlActivity', 'Logins/sec', '%SQL%:General Statistics', 1, 'Logins/sec', '', NULL, 1.0, 0 ), 
    ('ServerActivity', 'sqlActivity', 'Logouts/sec', '%SQL%:General Statistics', 1, 'Logouts/sec', '', NULL, 1.0, 0 ), 
    ('ServerActivity', 'sqlActivity', 'Transactions', '%SQL%:General Statistics', 1, 'Transactions', '', NULL, 1.0, 0 ), 
    ('ServerActivity', 'sqlActivity', 'User Connections', '%SQL%:General Statistics', 1, 'User Connections', '', NULL, 1.0, 0 ), 
    ('ServerActivity', 'sqlActivity', 'Batch Requests/sec', '%SQL%:SQL Statistics', 1, 'Batch Requests/sec', '', NULL, 1.0, 0 ), 
    ('ServerActivity', 'sqlActivity', 'SQL Compilations/sec', '%SQL%:SQL Statistics', 1, 'SQL Compilations/sec', '', NULL, 1.0, 0 ), 
    ('ServerActivity', 'sqlActivity', 'SQL Re-Compilations/sec', '%SQL%:SQL Statistics', 1, 'SQL Re-Compilations/sec', '', NULL, 1.0, 0 ), 

    ('SystemDiskUsage', 'diskSpeed', '[COUNTER_INSTANCE]', 'LogicalDisk', 0, 'Avg. Disk sec/Transfer', '%', NULL, 1.0, 0 ), 
    ('SystemDiskUsage', 'diskQueues', '[COUNTER_INSTANCE]', 'LogicalDisk', 0, 'Avg. Disk Queue Length', '%', NULL, 1.0, 0 ), 
    ('SystemDiskUsage', 'diskRates', '[COUNTER_INSTANCE]', 'LogicalDisk', 0, 'Disk Bytes/sec', '%', NULL, @CONVERT_BYTES_TO_MB, 0 ), 
    ('SystemDiskUsage', 'processStats', '[COUNTER_INSTANCE]', 'Process', 0, 'IO Read Bytes/sec', '%', '$(TARGETPROCESS)', 1.0, 0 ), 
    ('SystemDiskUsage', 'processStats', '[COUNTER_INSTANCE]', 'Process', 0, 'IO Write Bytes/sec', '%', '$(TARGETPROCESS)', 1.0, 0 ), 
    ('SystemDiskUsage', 'processStats', '[COUNTER_INSTANCE]', 'Process', 0, 'Disk Reads/sec', '%', '$(TARGETPROCESS)', 1.0, 0 ), 
    ('SystemDiskUsage', 'processStats', '[COUNTER_INSTANCE]', 'Process', 0, 'Disk Writes/sec', '%', '$(TARGETPROCESS)', 1.0, 0 ), 
    ('SystemDiskUsage', 'diskSpeed', '[COUNTER_INSTANCE]', 'LogicalDisk', 0, 'Avg. Disk sec/Transfer', '%', NULL, 1.0, 0 ), 
    ('SystemDiskUsage', 'diskQueues', '[COUNTER_INSTANCE]', 'LogicalDisk', 0, 'Avg. Disk Queue Length', '%', NULL, 1.0, 0 ), 
    ('SystemDiskUsage', 'diskRates', '[COUNTER_INSTANCE]', 'LogicalDisk', 0, 'Disk Bytes/sec', '%', NULL, @CONVERT_BYTES_TO_MB, 0 ), 

    ('SystemDiskUsagePivot', 'processStats', '[COUNTER_INSTANCE]', 'Process', 0, 'IO Read Bytes/sec', '%', '$(TARGETPROCESS)', 1.0, 0 ), 
    ('SystemDiskUsagePivot', 'processStats', '[COUNTER_INSTANCE]', 'Process', 0, 'IO Write Bytes/sec', '%', '$(TARGETPROCESS)', 1.0, 0 ), 
    ('SystemDiskUsagePivot', 'processStats', '[COUNTER_INSTANCE]', 'Process', 0, 'Disk Reads/sec', '%', '$(TARGETPROCESS)', 1.0, 0 ), 
    ('SystemDiskUsagePivot', 'processStats', '[COUNTER_INSTANCE]', 'Process', 0, 'Disk Writes/sec', '%', '$(TARGETPROCESS)', 1.0, 0 ), 

    ('SqlMemoryUsage', 'memoryRates', 'Page life expectancy', '%SQL%:Buffer Manager', 1, 'Page life expectancy', '', NULL, 1.0, 0 ), 

    ('SystemMemoryUsage', 'memoryUsage', 'Total Working Set', 'Process', 0, 'Working Set', '_Total', NULL, @CONVERT_BYTES_TO_MB, 0 ), 
    ('SystemMemoryUsage', 'memoryUsage', 'Cache Bytes', 'Memory', 0, 'Cache Bytes', '', NULL, @CONVERT_BYTES_TO_MB, 0 ), 
    ('SystemMemoryUsage', 'memoryUsage', 'Pool Nonpaged Bytes', 'Memory', 0, 'Pool Nonpaged Bytes', '', NULL, @CONVERT_BYTES_TO_MB, 0 ), 
    ('SystemMemoryUsage', 'memoryRates', 'Page Reads/sec', 'Memory', 0, 'Page Reads/sec', '', NULL, 1.0, 0 ), 
    ('SystemMemoryUsage', 'memoryRates', 'Page Writes/sec', 'Memory', 0, 'Page Writes/sec', '', NULL, 1.0, 0 ), 

    ('SystemMemoryUsagePivot', 'processStats', '[COUNTER_INSTANCE]', 'Process', 0, 'Working Set', '%', '$(TARGETPROCESS)', @CONVERT_BYTES_TO_MB, 0 ), 
    ('SystemMemoryUsagePivot', 'processStats', '[COUNTER_INSTANCE]', 'Process', 0, 'Private Bytes', '%', '$(TARGETPROCESS)', @CONVERT_BYTES_TO_MB, 0 ), 

    ('SystemCpuUsage', 'cpuUsage', 'CPU [COUNTER_INSTANCE]', 'Processor', 0, '% Processor Time', '%', NULL, 1.0, 0 ), 

    ('SystemCpuUsagePivot', 'processStats', '[COUNTER_INSTANCE]', 'Process', 0, '% Processor Time', '%', '$(TARGETPROCESS)', 1.0, 1 ), 
    ('SystemCpuUsagePivot', 'processStats', '[COUNTER_INSTANCE]', 'Process', 0, 'Thread Count', '%', '$(TARGETPROCESS)', 1.0, 0 ), 

    ('SqlActivity', 'sessionsAndConnections', 'User Connections', '%SQL%:General Statistics', 1, 'User Connections', '', NULL, 1.0, 0 ), 
    ('SqlActivity', 'sessionsAndConnections', 'Active Transactions', '%SQL%:Databases', 1, 'Active Transactions', '_Total', NULL, 1.0, 0 ), 
    ('SqlActivity', 'sessionsAndConnections', 'Active requests', '%SQL%:Workload Group Stats', 1, 'Active requests', '%', NULL, 1.0, 0 ), 
    ('SqlActivity', 'requestsAndCompilations', 'Batch Requests/sec', '%SQL%:SQL Statistics', 1, 'Batch Requests/sec', '', NULL, 1.0, 0 ), 
    ('SqlActivity', 'requestsAndCompilations', 'SQL Compilations/sec', '%SQL%:SQL Statistics', 1, 'SQL Compilations/sec', '', NULL, 1.0, 0 ), 
    ('SqlActivity', 'requestsAndCompilations', 'SQL Re-Compilations/sec', '%SQL%:SQL Statistics', 1, 'SQL Re-Compilations/sec', '', NULL, 1.0, 0 ), 
    ('SqlActivity', 'planCache', '[COUNTER_INSTANCE]', '%SQL%:Plan Cache', 1, 'Cache Hit Ratio', '%', NULL, 1.0, 0 ), 
    ('SqlActivity', 'tempDb', 'Free Space in tempdb (MB)', '%SQL%:Transactions', 1, 'Free Space in tempdb (KB)', '', NULL, @CONVERT_KB_TO_MB, 0 ), 
    ('SqlActivity', 'tempDb', 'Active Temp Tables', '%SQL%:General Statistics', 1, 'Active Temp Tables', '', NULL, 1.0, 0 ), 
    ('SqlActivity', 'tempDb', 'Transactions/sec', '%SQL%:Databases', 1, 'Transactions/sec', 'tempdb', NULL, 1.0, 0 )
GO


/**********************************************************************/
/* SNAPSHOTS SCHEMA                                                   */
/**********************************************************************/
PRINT ''
PRINT 'Create schema snapshots...'
PRINT ''
GO
IF (SCHEMA_ID('snapshots') IS NULL)
BEGIN
    -- Dynamic SQL here because we must skip the creation attempt if the schema already 
    -- exists, but we can't use a simple IF check because CREATE SCHEMA must be the first 
    -- statement in its batch. 
    DECLARE @sql nvarchar(128)
    SET @sql = 'CREATE SCHEMA snapshots'
    EXEC sp_executesql @sql
END
GO

--
-- PERFORMANCE COUNTERS COLLECTOR TYPE SUPPORT
--

-- This table holds information about instances of perf counters collected (their path and type)
IF (OBJECT_ID(N'[snapshots].[performance_counter_instances]', 'U') IS NULL)
BEGIN
	PRINT 'Creating table [snapshots].[performance_counter_instances]...'
	CREATE TABLE [snapshots].[performance_counter_instances](
		[performance_counter_id]			int IDENTITY NOT NULL,
		[path]								nvarchar(2048) NOT NULL,
		[object_name]						nvarchar(2048) NOT NULL,
		[counter_name]						nvarchar(2048) NOT NULL,
		[instance_name]						nvarchar(2048) NULL,
		[counter_type]						int NOT NULL,

		-- This constraint creation generates a warning. We will deal with the warning when fix for #130259 is provided
		CONSTRAINT [UN_performance_counter_path] UNIQUE
		(
			[path]
		) WITH (IGNORE_DUP_KEY = ON)
    )
END;
GO

-- performance_counter_instances.PK_performance_counter_instances
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('PK_performance_counter_instances', 'performance_counter_id', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'performance_counter_instances', 
    @object_type = 'PRIMARY KEY', @constraint_or_index_name = 'PK_performance_counter_instances', 
    @ignore_dup_key = 0, @clustered = 1;
GO

-- performance_counter_instances.IDX_performance_counter_instances1
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('IDX_performance_counter_instances1', 'object_name', 0, 0), 
    ('IDX_performance_counter_instances1', 'counter_name', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'performance_counter_instances', 
    @object_type = 'INDEX', @constraint_or_index_name = 'IDX_performance_counter_instances1', 
    @ignore_dup_key = 0, @clustered = 0;
GO


-- This table holds information about actual values collected for perf counters (formatted, raw values, and time when counter was collected)
IF (OBJECT_ID(N'[snapshots].[performance_counter_values]', 'U') IS NULL)
BEGIN
	PRINT 'Creating table [snapshots].[performance_counter_values]...'
	CREATE TABLE [snapshots].[performance_counter_values](
		[performance_counter_instance_id]	int NOT NULL,
		[snapshot_id]						int NOT NULL,					
		[collection_time]					datetimeoffset(7) NOT NULL,
		[formatted_value]					float NOT NULL,
		[raw_value_first]					bigint NOT NULL,
		[raw_value_second]					bigint NULL,
	) ON [PRIMARY];
	
    ALTER TABLE [snapshots].[performance_counter_values]
    ADD CONSTRAINT [CHK_performance_counter_values_check_operator] CHECK (core.fn_check_operator(snapshot_id) = 1)
END;
GO

-- performance_counter_values.PK_performance_counter_values
-- 
-- Ignore duplicate keys on primary key and not fail uploads because of that.
-- This is needed because in some cases we may have collection item that
-- collects the same counter twice, for valid reasons. To not force users
-- to provide some exclusion lists, we will ignore duplicates. 
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('PK_performance_counter_values', 'performance_counter_instance_id', 0, 0), 
    ('PK_performance_counter_values', 'snapshot_id', 0, 0), 
    ('PK_performance_counter_values', 'collection_time', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'performance_counter_values', 
    @object_type = 'PRIMARY KEY', @constraint_or_index_name = 'PK_performance_counter_values', 
    @ignore_dup_key = 1, @clustered = 1;
GO

-- performance_counter_values.IDX_performance_counter_values1
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('IDX_performance_counter_values1', 'snapshot_id', 0, 0), 
    ('IDX_performance_counter_values1', 'performance_counter_instance_id', 0, 0), 
    ('IDX_performance_counter_values1', 'collection_time', 0, 0), 
    ('IDX_performance_counter_values1', 'formatted_value', 1, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'performance_counter_values', 
    @object_type = 'INDEX', @constraint_or_index_name = 'IDX_performance_counter_values1', 
    @ignore_dup_key = 0, @clustered = 0;
GO

-- performance_counter_values.FK_performance_counter_values_snapshot_id
IF OBJECT_ID ('snapshots.FK_performance_counter_values_snapshot_id', 'F') IS NULL
BEGIN
    RAISERROR ('Creating foreign key [FK_performance_counter_values_snapshot_id] on snapshots.performance_counter_values ...', 0, 1) WITH NOWAIT;
    ALTER TABLE [snapshots].[performance_counter_values]
        ADD CONSTRAINT [FK_performance_counter_values_snapshot_id] FOREIGN KEY(snapshot_id)
        REFERENCES [core].[snapshots_internal] (snapshot_id) ON DELETE CASCADE
END;
GO

-- performance_counter_values.FK_performance_counter_values_instance_id
IF OBJECT_ID ('snapshots.FK_performance_counter_values_snapshot_id', 'F') IS NULL
BEGIN
    RAISERROR ('Creating foreign key [FK_performance_counter_values_instance_id] on snapshots.performance_counter_values ...', 0, 1) WITH NOWAIT;
    ALTER TABLE [snapshots].[performance_counter_values]
        ADD CONSTRAINT [FK_performance_counter_values_instance_id] FOREIGN KEY(performance_counter_instance_id)
        REFERENCES [snapshots].[performance_counter_instances] (performance_counter_id) ON DELETE CASCADE
END;
GO


-- This view shows user friendly information about performance counters
IF (OBJECT_ID(N'[snapshots].[performance_counters]', 'V') IS NOT NULL)
BEGIN
	PRINT 'Dropping view [snapshots].[performance_counters]...'
	DROP VIEW [snapshots].[performance_counters]
END

PRINT 'Creating view [snapshots].[performance_counters]...'
GO
CREATE VIEW [snapshots].[performance_counters]
AS
SELECT     
	pci.performance_counter_id, 
	pcv.snapshot_id AS snapshot_id, 
	pcv.collection_time AS collection_time,
	pci.path AS path, 
	pci.object_name AS performance_object_name,
	pci.counter_name AS performance_counter_name,
	pci.instance_name AS performance_instance_name,
	pcv.formatted_value AS formatted_value, 
	pcv.raw_value_first, 
	pcv.raw_value_second
FROM
	snapshots.performance_counter_instances pci
	INNER JOIN snapshots.performance_counter_values pcv ON pci.performance_counter_id = pcv.performance_counter_instance_id
GO

-- Function to obtain user friendly formatted perf counters values for a given instance and time window
IF (OBJECT_ID(N'snapshots.fn_get_performance_counters', 'IF') IS NOT NULL)
BEGIN
    PRINT 'Dropping function [snapshots].[fn_get_performance_counters] ...'
    DROP FUNCTION [snapshots].[fn_get_performance_counters]
END
GO 

PRINT 'Creating function [snapshots].[fn_get_performance_counters] ...'
GO
CREATE FUNCTION [snapshots].[fn_get_performance_counters]  
(
    @instance_name       sysname,
    @start_time          datetimeoffset(7) = NULL,
    @end_time            datetimeoffset(7) = NULL
) 
RETURNS TABLE
AS
RETURN
(
    SELECT 
        pc.performance_counter_id AS performance_counter_id,
        pc.collection_time AS collection_time,
        pc.path AS path,
		pc.performance_object_name AS performance_object_name,
		pc.performance_counter_name AS performance_counter_name,
		pc.performance_instance_name AS performance_instance_name,
        pc.formatted_value AS formatted_value
    FROM [snapshots].[performance_counters] as pc
	JOIN [core].[snapshots] s on s.snapshot_id = pc.snapshot_id
    WHERE
		@instance_name = s.instance_name AND
		ISNULL(@start_time,CAST (0 AS DATETIME)) <= pc.collection_time AND
		ISNULL(@end_time,GETDATE()) >= pc.collection_time
)
GO

-- Function to obtain user friendly statistics about a perf counter for a given instance, counter path and time window
IF (OBJECT_ID(N'snapshots.fn_get_performance_counter_statistics', 'IF') IS NOT NULL)
BEGIN
    PRINT 'Dropping function [snapshots].[fn_get_performance_counter_statistics] ...'
    DROP FUNCTION [snapshots].[fn_get_performance_counter_statistics]
END
GO 

PRINT 'Creating function [snapshots].[fn_get_performance_counter_statistics] ...'
GO
CREATE FUNCTION [snapshots].[fn_get_performance_counter_statistics]  
(
    @instance_name       sysname,
    @path_pattern		 nvarchar(2048),
    @start_time          datetimeoffset(7) = NULL,
    @end_time            datetimeoffset(7) = NULL
) 
RETURNS TABLE
AS
RETURN
(
    SELECT 
		pc.path						as path,
		MIN(pc.formatted_value)		as minimum_value,
		MAX(pc.formatted_value)		as maximum_value,
        AVG(pc.formatted_value)		as average_value,
		STDEV(pc.formatted_value)	as standard_deviation,
		VAR(pc.formatted_value)		as statistical_variance
    FROM [snapshots].[performance_counters] as pc
	JOIN [core].[snapshots] s on s.snapshot_id = pc.snapshot_id
    WHERE
		s.instance_name = @instance_name AND
		pc.path LIKE @path_pattern AND
		pc.collection_time >= @start_time AND
		pc.collection_time <= @end_time
	GROUP BY pc.path
)
GO



--
-- SQLTRACE COLLECTOR TYPE SUPPORT
--

-- This table holds information about captured traces
IF (OBJECT_ID(N'[snapshots].[trace_info]', 'U') IS NULL)
BEGIN
    PRINT 'Creating table [snapshots].[trace_info]...'
    CREATE TABLE [snapshots].[trace_info] (
        trace_info_id                 int IDENTITY NOT NULL,
        source_id                     int NOT NULL,      -- id of source_info record corresponding to source of this data
        collection_item_id            int NOT NULL,      -- id of the trace on the target instance
        last_snapshot_id              int NULL,          -- references core.snapshots table. Identifies the most recent snapshot id that was generated for this trace
        start_time                    datetime NULL,     -- time when this trace was started on the target machine
        last_event_sequence           bigint NULL,       -- event sequence of the most recent event for this trace was captured. 
        is_running                    bit NULL,          -- 0 - trace is stopped, 1 - trace is running
        event_count                   bigint NULL,       -- total number of events captured by this trace
        dropped_event_count           int NULL,          -- total number of events dropped by this trace.
    );
END;
GO

-- trace_info.PK_trace_info
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('PK_trace_info', 'trace_info_id', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'trace_info', 
    @object_type = 'PRIMARY KEY', @constraint_or_index_name = 'PK_trace_info', 
    @ignore_dup_key = 0, @clustered = 1;
GO

-- trace_info.FK_trace_info_last_snapshot_id
IF OBJECT_ID ('snapshots.FK_trace_info_last_snapshot_id', 'F') IS NULL
BEGIN
    RAISERROR ('Creating foreign key [FK_trace_info_last_snapshot_id] on snapshots.trace_info ...', 0, 1) WITH NOWAIT;
    ALTER TABLE [snapshots].[trace_info]
        ADD CONSTRAINT [FK_trace_info_last_snapshot_id] FOREIGN KEY(last_snapshot_id)
        REFERENCES [core].[snapshots_internal] (snapshot_id) ON DELETE CASCADE
END;
GO

-- trace_info.FK_trace_info_last_snapshot_id
IF OBJECT_ID ('snapshots.FK_trace_info_source_id', 'F') IS NULL
BEGIN
    RAISERROR ('Creating foreign key [FK_trace_info_source_id] on snapshots.trace_info ...', 0, 1) WITH NOWAIT;
    ALTER TABLE [snapshots].[trace_info]
        ADD CONSTRAINT [FK_trace_info_source_id] FOREIGN KEY(source_id)
        REFERENCES [core].[source_info_internal] (source_id)
END;
GO


-- This table holds all trace data 
IF (OBJECT_ID(N'[snapshots].[trace_data]', 'U') IS NULL)
BEGIN
    PRINT 'Creating table [snapshots].[trace_data]...'
    CREATE TABLE [snapshots].[trace_data] (
        trace_info_id                   int NOT NULL,    -- references id of the trace from trace_info table
        snapshot_id                     int NOT NULL,    -- references snapshot_id from core.snapshots
        -- Following all all trace columns that match the list from sys.trace_columns system view
        TextData                        nvarchar(max) NULL,
        BinaryData                      varbinary(max) NULL,
        DatabaseID                      int NULL,
        TransactionID                   bigint NULL,
        LineNumber                      int NULL,
        NTUserName                      nvarchar(256) NULL,
        NTDomainName                    nvarchar(256) NULL,
        HostName                        nvarchar(256) NULL,
        ClientProcessID                 int NULL,
        ApplicationName                 nvarchar(256) NULL,
        LoginName                       nvarchar(256) NULL,
        SPID                            int NULL,
        Duration                        bigint NULL,
        StartTime                       datetimeoffset(7) NULL,
        EndTime                         datetimeoffset(7) NULL,
        Reads                           bigint NULL,
        Writes                          bigint NULL,
        CPU                             int NULL,
        Permissions                     bigint NULL,
        Severity                        int NULL,
        EventSubClass                   int NULL,
        ObjectID                        int NULL,
        Success                         int NULL,
        IndexID                         int NULL,
        IntegerData                     int NULL,
        ServerName                      nvarchar(256) NULL,
        EventClass                      int NULL,
        ObjectType                      int NULL,
        NestLevel                       int NULL,
        State                           int NULL,
        Error                           int NULL,
        Mode                            int NULL,
        Handle                          int NULL,
        ObjectName                      nvarchar(256) NULL,
        DatabaseName                    nvarchar(256) NULL,
        FileName                        nvarchar(256) NULL,
        OwnerName                       nvarchar(256) NULL,
        RoleName                        nvarchar(256) NULL,
        TargetUserName                  nvarchar(256) NULL,
        DBUserName                      nvarchar(256) NULL,
        LoginSid                        varbinary(max) NULL,
        TargetLoginName                 nvarchar(256) NULL,
        TargetLoginSid                  varbinary(max) NULL,
        ColumnPermissions               int NULL,
        LinkedServerName                nvarchar(256) NULL,
        ProviderName                    nvarchar(256) NULL,
        MethodName                      nvarchar(256) NULL,
        RowCounts                       bigint NULL,
        RequestID                       int NULL,
        XactSequence                    bigint NULL,
        EventSequence                   bigint NOT NULL,
        BigintData1                     bigint NULL,
        BigintData2                     bigint NULL,
        GUID                            uniqueidentifier NULL,
        IntegerData2                    int NULL,
        ObjectID2                       bigint NULL,
        Type                            int NULL,
        OwnerID                         int NULL,
        ParentName                      nvarchar(256) NULL,
        IsSystem                        int NULL,
        Offset                          int NULL,
        SourceDatabaseID                int NULL,
        SqlHandle                       varbinary(64) NULL,
        SessionLoginName                nvarchar(256) NULL,
        PlanHandle                      varbinary(64) NULL, 
        GroupID                         int NULL

    )  ON [PRIMARY];

    ALTER TABLE [snapshots].[trace_data]
        ADD CONSTRAINT [CHK_trace_data_check_operator] CHECK (core.fn_check_operator(snapshot_id) = 1);
END;
GO

-- trace_data.IDX_trace_data_EventSequence
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('IDX_trace_data_EventSequence', 'snapshot_id', 0, 0), 
    ('IDX_trace_data_EventSequence', 'EventSequence', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'trace_data', 
    @object_type = 'INDEX', @constraint_or_index_name = 'IDX_trace_data_EventSequence', 
    @ignore_dup_key = 0, @clustered = 1;
GO

-- trace_data.IDX_trace_data_trace_info_id
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('IDX_trace_data_trace_info_id', 'trace_info_id', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'trace_data', 
    @object_type = 'INDEX', @constraint_or_index_name = 'IDX_trace_data_trace_info_id', 
    @ignore_dup_key = 0, @clustered = 0;
GO

-- trace_data.IDX_trace_data_EventSequence
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('IDX_trace_data_StartTime_EventClass', 'StartTime', 0, 0), 
    ('IDX_trace_data_StartTime_EventClass', 'EventClass', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'trace_data', 
    @object_type = 'INDEX', @constraint_or_index_name = 'IDX_trace_data_StartTime_EventClass', 
    @ignore_dup_key = 0, @clustered = 0;
GO

-- trace_data.FK_trace_data_trace_info_id
IF OBJECT_ID ('snapshots.FK_trace_data_trace_info_id', 'F') IS NULL
BEGIN
    RAISERROR ('Creating foreign key [FK_trace_data_trace_info_id] on snapshots.trace_data ...', 0, 1) WITH NOWAIT;
    ALTER TABLE [snapshots].[trace_data]
        ADD CONSTRAINT [FK_trace_data_trace_info_id] FOREIGN KEY(trace_info_id)
        REFERENCES [snapshots].[trace_info] (trace_info_id);
END;
GO

-- trace_data.FK_trace_data_snapshot_id
IF OBJECT_ID ('snapshots.FK_trace_data_snapshot_id', 'F') IS NULL
BEGIN
    RAISERROR ('Creating foreign key [FK_trace_data_snapshot_id] on snapshots.trace_data ...', 0, 1) WITH NOWAIT;
    ALTER TABLE [snapshots].[trace_data]
        ADD CONSTRAINT [FK_trace_data_snapshot_id] FOREIGN KEY(snapshot_id)
        REFERENCES [core].[snapshots_internal] (snapshot_id) ON DELETE CASCADE;
END;
GO


-- This stored proc inserts a new row int the snapshots.trace_info table
IF (NOT OBJECT_ID(N'snapshots.sp_trace_get_info', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[sp_trace_get_info] ...'
    DROP PROCEDURE [snapshots].[sp_trace_get_info]
END
GO 

PRINT 'Creating procedure [snapshots].[sp_trace_get_info] ...'
GO
CREATE PROCEDURE [snapshots].[sp_trace_get_info]
    @source_id           int,               -- references source of data from core.source_info_internal
    @collection_item_id  int,               -- idenitfies the collection item id within the source info
    @start_time          datetime,          -- time when the trace has started
    @last_event_sequence bigint OUTPUT,     -- returns the event sequence number for last trace event uploaded, or 0
    @trace_info_id       int OUTPUT         -- returns id of trace_info record
AS
BEGIN
    SET NOCOUNT ON;

    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'mdw_writer'), 0) = 1) AND NOT (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1))
    BEGIN
        RAISERROR(14677, 16, -1, 'mdw_writer');
        RETURN(1); -- Failure
    END;

    -- Parameters check - mandatory parameters
    IF (@source_id IS NULL)
    BEGIN
        RAISERROR(14200, -1, -1, '@source_id')
        RETURN(1) -- Failure
    END;

    IF (@collection_item_id IS NULL)
    BEGIN
        RAISERROR(14200, -1, -1, '@collection_item_id')
        RETURN(1) -- Failure
    END;

    IF (@start_time IS NULL)
    BEGIN
        RAISERROR(14200, -1, -1, '@start_time')
        RETURN(1) -- Failure
    END;

    SELECT 
        @trace_info_id = trace_info_id,
        @last_event_sequence = last_event_sequence 
    FROM snapshots.trace_info ti
    WHERE 
        ti.source_id = @source_id
        AND ti.collection_item_id = @collection_item_id
        AND ti.is_running = 1
        AND ti.start_time = @start_time;

    IF (@trace_info_id IS NULL)
    BEGIN
        SELECT @last_event_sequence = 0;

        -- Insert new record
        INSERT INTO [snapshots].[trace_info]
        (
            source_id,
            collection_item_id,
            start_time,
            is_running,
            last_event_sequence
        )
        VALUES
        (
            @source_id,
            @collection_item_id,
            @start_time,
            1,
            @last_event_sequence
        );
        SELECT @trace_info_id = SCOPE_IDENTITY();
    END;

    RETURN (0);
END
GO


-- This stored proc updates a row in the snapshots.trace_info table
IF (NOT OBJECT_ID(N'snapshots.sp_trace_update_info', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[sp_trace_update_info] ...'
    DROP PROCEDURE [snapshots].[sp_trace_update_info]
END
GO 

PRINT 'Creating procedure [snapshots].[sp_trace_update_info] ...'
GO
CREATE PROCEDURE [snapshots].[sp_trace_update_info]
    @trace_info_id       int,
    @snapshot_id         int,
    @last_event_sequence bigint,
    @is_running          bit,
    @event_count         bigint,
    @dropped_event_count int
AS
BEGIN
    SET NOCOUNT ON;

    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'mdw_writer'), 0) = 1) AND NOT (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1))
    BEGIN
        RAISERROR(14677, 16, -1, 'mdw_writer');
        RETURN(1); -- Failure
    END;

    -- Parameters check - mandatory parameters
    IF (@trace_info_id IS NULL)
    BEGIN
        RAISERROR(14200, -1, -1, '@trace_info_id')
        RETURN(1) -- Failure
    END;

    IF NOT EXISTS (SELECT trace_info_id from snapshots.trace_info where trace_info_id = @trace_info_id)
    BEGIN
        DECLARE @trace_info_id_as_char NVARCHAR(10)
        SELECT @trace_info_id_as_char = CONVERT(NVARCHAR(36), @trace_info_id)
        RAISERROR(14679, -1, -1, N'@trace_info_id', @trace_info_id_as_char)
        RETURN(1) -- Failure
    END;

    IF (@snapshot_id IS NULL)
    BEGIN
        RAISERROR(14200, -1, -1, '@snapshot_id')
        RETURN(1) -- Failure
    END;

    IF NOT EXISTS (SELECT snapshot_id from core.snapshots where snapshot_id = @snapshot_id)
    BEGIN
        DECLARE @snapshot_id_as_char NVARCHAR(36)
        SELECT @snapshot_id_as_char = CONVERT(NVARCHAR(36), @snapshot_id)
        RAISERROR(14679, -1, -1, N'@snapshot_id', @snapshot_id_as_char)
        RETURN(1) -- Failure
    END;

    IF (@last_event_sequence IS NULL)
    BEGIN
        RAISERROR(14200, -1, -1, '@last_event_sequence')
        RETURN(1) -- Failure
    END;

    IF (@is_running IS NULL)
    BEGIN
        RAISERROR(14200, -1, -1, '@is_running')
        RETURN(1) -- Failure
    END;

    IF (@event_count IS NULL)
    BEGIN
        RAISERROR(14200, -1, -1, '@event_count')
        RETURN(1) -- Failure
    END;

    IF (@dropped_event_count IS NULL)
    BEGIN
        RAISERROR(14200, -1, -1, '@dropped_event_count')
        RETURN(1) -- Failure
    END;

    -- Update existing record
    UPDATE [snapshots].[trace_info]
    SET
        last_snapshot_id = @snapshot_id,
        last_event_sequence = @last_event_sequence,
        is_running = @is_running,
        event_count = ISNULL(event_count,0) + @event_count,
        dropped_event_count = @dropped_event_count
    WHERE
        trace_info_id = @trace_info_id;

    RETURN(0);
END
GO


-- This function returns all data captured for the specified trace
IF (NOT OBJECT_ID(N'snapshots.fn_trace_gettable', 'IF') IS NULL)
BEGIN
    PRINT 'Dropping function [snapshots].[fn_trace_gettable] ...'
    DROP FUNCTION [snapshots].[fn_trace_gettable]
END
GO 

PRINT 'Creating function [snapshots].[fn_trace_gettable] ...'
GO
CREATE FUNCTION [snapshots].[fn_trace_gettable]  
(
    @trace_info_id       int,
    @start_time          datetimeoffset(7) = NULL,
    @end_time            datetimeoffset(7) = NULL
) 
RETURNS TABLE
AS
RETURN
(
    SELECT 
        TextData,
        BinaryData,
        DatabaseID,
        TransactionID,
        LineNumber,
        NTUserName,
        NTDomainName,
        HostName,
        ClientProcessID,
        ApplicationName,
        LoginName,
        SPID,
        Duration,
        StartTime,
        EndTime,
        Reads,
        Writes,
        CPU,
        Permissions,
        Severity,
        EventSubClass,
        ObjectID,
        Success,
        IndexID,
        IntegerData,
        ServerName,
        EventClass,
        ObjectType,
        NestLevel,
        State,
        Error,
        Mode,
        Handle,
        ObjectName,
        DatabaseName,
        FileName,
        OwnerName,
        RoleName,
        TargetUserName,
        DBUserName,
        LoginSid,
        TargetLoginName,
        TargetLoginSid,
        ColumnPermissions,
        LinkedServerName,
        ProviderName,
        MethodName,
        RowCounts,
        RequestID,
        XactSequence,
        EventSequence,
        BigintData1,
        BigintData2,
        GUID,
        IntegerData2,
        ObjectID2,
        Type,
        OwnerID,
        ParentName,
        IsSystem,
        Offset,
        SourceDatabaseID,
        SqlHandle,
        SessionLoginName,
        PlanHandle
    FROM snapshots.trace_data
    WHERE
        trace_info_id = @trace_info_id
        AND StartTime >= ISNULL(@start_time, '1753-01-01')
        AND StartTime <= ISNULL(@end_time, '9999-12-31')
)
GO

--
-- GENERAL TABLES FOR SYSTEM COLLECTION SETS
--

-- Association of batch text with sql_handle and object_name
--
IF (OBJECT_ID(N'snapshots.notable_query_text', 'U') IS NULL)
BEGIN
    PRINT 'Creating table [snapshots].[notable_query_text]...'
    CREATE TABLE [snapshots].[notable_query_text] (
        [sql_handle]        varbinary(64) NOT NULL,
        [database_id]       smallint      NULL,
        [object_id]         int           NULL,
        [object_name]       nvarchar(128) NULL,
        [sql_text]          nvarchar(max) NULL,
        [source_id]         int           NOT NULL
    ) ON [PRIMARY]
END
GO

-- notable_query_text.PK_notable_query_text
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('PK_notable_query_text', 'source_id', 0, 0), 
    ('PK_notable_query_text', 'sql_handle', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'notable_query_text', 
    @object_type = 'PRIMARY KEY', @constraint_or_index_name = 'PK_notable_query_text', 
    @ignore_dup_key = 1, @clustered = 1; -- IGNORE_DUP_KEY
GO

-- notable_query_text.FK_distinct_query_to_handle_notable_query_text
IF OBJECT_ID ('snapshots.FK_notable_query_text_source_info_internal', 'F') IS NULL
BEGIN
    RAISERROR ('Creating foreign key [FK_distinct_query_to_handle_notable_query_text] on snapshots.notable_query_text ...', 0, 1) WITH NOWAIT;
    ALTER TABLE [snapshots].[notable_query_text] ADD CONSTRAINT [FK_notable_query_text_source_info_internal] FOREIGN KEY([source_id])
        REFERENCES [core].[source_info_internal] (source_id)
        ON DELETE CASCADE
END;


-- Association of batch plan with sql_handle and plan_handle and object_name
--
IF (OBJECT_ID(N'snapshots.notable_query_plan', 'U') IS NULL)
BEGIN
    PRINT 'Creating table [snapshots].[notable_query_plan]...'
    CREATE TABLE [snapshots].[notable_query_plan] (
        [sql_handle]                varbinary(64)   NOT NULL,
        [plan_handle]               varbinary(64)   NOT NULL,
        [statement_start_offset]    int             NOT NULL,
        [statement_end_offset]      int             NOT NULL,
        [plan_generation_num]       bigint          NOT NULL,
        [creation_time]             datetimeoffset(7)  NOT NULL,
        [database_id]               smallint        NULL,
        [object_id]                 int             NULL,
        [object_name]               nvarchar(128)   NULL,
        [query_plan]                nvarchar(max)   NULL,
        [source_id]                 int             NOT NULL,
    ) ON [PRIMARY]
END;

-- notable_query_plan.PK_notable_query_plan
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('PK_notable_query_plan', 'source_id', 0, 0), 
    ('PK_notable_query_plan', 'sql_handle', 0, 0), 
    ('PK_notable_query_plan', 'plan_handle', 0, 0), 
    ('PK_notable_query_plan', 'statement_start_offset', 0, 0), 
    ('PK_notable_query_plan', 'statement_end_offset', 0, 0), 
    ('PK_notable_query_plan', 'creation_time', 0, 0), 
    ('PK_notable_query_plan', 'plan_generation_num', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'notable_query_plan', 
    @object_type = 'PRIMARY KEY', @constraint_or_index_name = 'PK_notable_query_plan', 
    @ignore_dup_key = 1, @clustered = 1;
GO

-- notable_query_plan.IDX_notable_query_plan_plan_handle
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('IDX_notable_query_plan_plan_handle', 'source_id', 0, 0), 
    ('IDX_notable_query_plan_plan_handle', 'plan_handle', 0, 0), 
    ('IDX_notable_query_plan_plan_handle', 'statement_start_offset', 0, 0), 
    ('IDX_notable_query_plan_plan_handle', 'statement_end_offset', 0, 0), 
    ('IDX_notable_query_plan_plan_handle', 'creation_time', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'notable_query_plan', 
    @object_type = 'INDEX', @constraint_or_index_name = 'IDX_notable_query_plan_plan_handle', 
    @ignore_dup_key = 0, @clustered = 0;
GO

-- notable_query_plan.FK_notable_query_plan_source_info_internal
IF OBJECT_ID ('snapshots.FK_notable_query_plan_source_info_internal', 'F') IS NULL
BEGIN
    RAISERROR ('Creating foreign key [FK_notable_query_plan_source_info_internal] on snapshots.notable_query_plan ...', 0, 1) WITH NOWAIT;
    ALTER TABLE [snapshots].[notable_query_plan] ADD CONSTRAINT [FK_notable_query_plan_source_info_internal] FOREIGN KEY([source_id])
        REFERENCES [core].[source_info_internal] (source_id)
        ON DELETE CASCADE;
END;
GO


IF (OBJECT_ID(N'snapshots.distinct_queries', 'U') IS NULL)
BEGIN
    PRINT 'Creating table [snapshots].[distinct_queries]...'
    CREATE TABLE [snapshots].[distinct_queries] (
        [distinct_query_hash]   bigint          NOT NULL,
        [distinct_sql_text]     nvarchar(512)   NOT NULL,
        [source_id]             int             NOT NULL,
    );
END;
GO

-- distinct_queries.PK_distinct_queries
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('PK_distinct_queries', 'source_id', 0, 0), 
    ('PK_distinct_queries', 'distinct_query_hash', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'distinct_queries', 
    @object_type = 'PRIMARY KEY', @constraint_or_index_name = 'PK_distinct_queries', 
    @ignore_dup_key = 0, @clustered = 1;
GO

-- distinct_queries.FK_distinct_queries_source_info_internal
IF OBJECT_ID ('snapshots.FK_distinct_queries_source_info_internal', 'F') IS NULL
BEGIN
    RAISERROR ('Creating foreign key [FK_distinct_queries_source_info_internal] on snapshots.distinct_queries ...', 0, 1) WITH NOWAIT;
    ALTER TABLE [snapshots].[distinct_queries] ADD CONSTRAINT [FK_distinct_queries_source_info_internal] FOREIGN KEY([source_id])
        REFERENCES [core].[source_info_internal] (source_id)
        ON DELETE CASCADE
END;
GO


IF (OBJECT_ID(N'snapshots.distinct_query_to_handle', 'U') IS NULL)
BEGIN
    PRINT 'Creating table [snapshots].[distinct_query_to_handle]...'
    CREATE TABLE [snapshots].[distinct_query_to_handle] (
        [distinct_query_hash]   bigint          NOT NULL,
        [sql_handle]            varbinary(64)   NOT NULL,
        [source_id]             int             NOT NULL
    ) ON [PRIMARY]
END
GO

-- distinct_query_to_handle.PK_distinct_query_to_handle
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('PK_distinct_query_to_handle', 'source_id', 0, 0), 
    ('PK_distinct_query_to_handle', 'distinct_query_hash', 0, 0), 
    ('PK_distinct_query_to_handle', 'sql_handle', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'distinct_query_to_handle', 
    @object_type = 'PRIMARY KEY', @constraint_or_index_name = 'PK_distinct_query_to_handle', 
    @ignore_dup_key = 0, @clustered = 1;
GO

-- distinct_query_to_handle.FK_distinct_query_to_handle_notable_query_text
IF OBJECT_ID ('snapshots.FK_distinct_query_to_handle_notable_query_text', 'F') IS NULL
BEGIN
    RAISERROR ('Creating foreign key snapshots.distinct_query_to_handle.FK_distinct_query_to_handle_notable_query_text...', 0, 1) WITH NOWAIT
    ALTER TABLE [snapshots].[distinct_query_to_handle] ADD CONSTRAINT [FK_distinct_query_to_handle_notable_query_text] FOREIGN KEY([source_id], [sql_handle])
        REFERENCES [snapshots].[notable_query_text] ([source_id], [sql_handle])
END;
GO

-- distinct_query_to_handle.FK_distinct_query_to_handle_distinct_queries
IF OBJECT_ID ('snapshots.FK_distinct_query_to_handle_distinct_queries', 'F') IS NULL
BEGIN
    RAISERROR ('Creating foreign key snapshots.distinct_query_to_handle.FK_distinct_query_to_handle_distinct_queries...', 0, 1) WITH NOWAIT
    ALTER TABLE [snapshots].[distinct_query_to_handle] ADD CONSTRAINT [FK_distinct_query_to_handle_distinct_queries] FOREIGN KEY([source_id], [distinct_query_hash])
        REFERENCES [snapshots].[distinct_queries] ([source_id], [distinct_query_hash])
END;
GO

-- distinct_query_to_handle.FK_distinct_query_to_handle_source_info_internal
IF OBJECT_ID ('snapshots.FK_distinct_query_to_handle_source_info_internal', 'F') IS NULL
BEGIN
    RAISERROR ('Creating foreign key snapshots.distinct_query_to_handle.FK_distinct_query_to_handle_source_info_internal...', 0, 1) WITH NOWAIT
    ALTER TABLE [snapshots].[distinct_query_to_handle] ADD CONSTRAINT [FK_distinct_query_to_handle_source_info_internal] FOREIGN KEY([source_id])
        REFERENCES [core].[source_info_internal] (source_id)
        ON DELETE CASCADE
END;
GO


-- This function returns text and database and object context for a query identified by a sql_handle
IF (NOT OBJECT_ID(N'snapshots.fn_get_query_text', 'TF') IS NULL)
BEGIN
    PRINT 'Dropping function [snapshots].[fn_get_query_text] ...'
    DROP FUNCTION [snapshots].[fn_get_query_text]
END
GO 

PRINT 'Creating function [snapshots].[fn_get_query_text] ...'
GO
CREATE FUNCTION [snapshots].[fn_get_query_text](
    @source_id int,
    @sql_handle varbinary(64), 
    @statement_start_offset int, 
    @statement_end_offset int
)
RETURNS @query_text TABLE (database_id smallint NULL, object_id int NULL, object_name sysname NULL, query_text nvarchar(max) NULL)

BEGIN
	IF @sql_handle IS NOT NULL AND 
       EXISTS (SELECT sql_handle FROM snapshots.notable_query_text WHERE sql_handle = @sql_handle AND source_id = @source_id)
	BEGIN
        DECLARE @start int
        DECLARE @end int

		-- statement_end_offset is zero prior to beginning query execution (e.g., compilation)
		SELECT 
			@start = ISNULL(@statement_start_offset, 0), 
			@end = CASE WHEN @statement_end_offset IS NULL OR @statement_end_offset = 0 THEN -1
						ELSE @statement_end_offset 
			       END

		INSERT INTO @query_text 
        (
            database_id, 
            object_id, 
            object_name, 
            query_text
        ) 
		SELECT 
            t.database_id,
            t.object_id,
            t.object_name,
            LTRIM(SUBSTRING(t.sql_text, @start / 2 + 1, ((CASE WHEN @end = -1 THEN DATALENGTH(t.sql_text) ELSE @end END) - @start) / 2))
        FROM
            snapshots.notable_query_text t
        WHERE
            t.sql_handle = @sql_handle
            AND t.source_id = @source_id
	END

	RETURN
END
GO

-- This stored procedure returns all rows from notable_query_text table 
-- where sql_text value is NULL, meaning we did not caputure the text for that query yet.
IF (NOT OBJECT_ID(N'snapshots.sp_get_unknown_query_text', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[sp_get_unknown_query_text] ...'
    DROP PROCEDURE [snapshots].[sp_get_unknown_query_text]
END
GO 

PRINT 'Creating procedure [snapshots].[sp_get_unknown_query_text] ...'
GO
CREATE PROCEDURE [snapshots].[sp_get_unknown_query_text]
    @source_id  int
AS
BEGIN
    SET NOCOUNT ON;

    SELECT
        [source_id],
        [sql_handle]
    FROM
        [snapshots].[notable_query_text]
    WHERE
        [source_id] = @source_id
        AND [sql_text] IS NULL
END;
GO

-- This stored procedure returns all rows from notable_query_plan table 
-- where sql_text value is NULL, meaning we did not caputure the plan for that query yet.
IF (NOT OBJECT_ID(N'snapshots.sp_get_unknown_query_plan', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[sp_get_unknown_query_plan] ...'
    DROP PROCEDURE [snapshots].[sp_get_unknown_query_plan]
END
GO 

PRINT 'Creating procedure [snapshots].[sp_get_unknown_query_plan] ...'
GO
CREATE PROCEDURE [snapshots].[sp_get_unknown_query_plan]
    @source_id       int
AS
BEGIN
    SET NOCOUNT ON;

    SELECT
        [source_id],
        [sql_handle],
        [plan_handle],
	    [statement_start_offset],
	    [statement_end_offset],
        [plan_generation_num]
    FROM
        [snapshots].[notable_query_plan]
    WHERE
        [source_id] = @source_id
        AND [query_plan] IS NULL
END;
GO

-- Updates a single row with new text for the sql_handle
IF (NOT OBJECT_ID(N'snapshots.sp_update_query_text', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[sp_update_query_text] ...'
    DROP PROCEDURE [snapshots].[sp_update_query_text]
END
GO 

PRINT 'Creating procedure [snapshots].[sp_update_query_text] ...'
GO
CREATE PROCEDURE [snapshots].[sp_update_query_text]
    @source_id       int,
    @sql_handle      varbinary(64),
    @database_id     smallint     ,
    @object_id       int          ,
    @object_name     nvarchar(128),
    @sql_text        nvarchar(max)
AS
BEGIN
    SET NOCOUNT ON;

    UPDATE [snapshots].[notable_query_text]
    SET
        database_id = @database_id,
        object_id   = @object_id,
        object_name = @object_name,
        sql_text    = @sql_text
    WHERE
        source_id = @source_id 
        AND sql_handle = @sql_handle
        
END;
GO

-- Updates a single row with new plan for the plan_handle
IF (NOT OBJECT_ID(N'snapshots.sp_update_query_plan', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[sp_update_query_plan] ...'
    DROP PROCEDURE [snapshots].[sp_update_query_plan]
END
GO 

PRINT 'Creating procedure [snapshots].[sp_update_query_plan] ...'
GO
CREATE PROCEDURE [snapshots].[sp_update_query_plan]
    @source_id                 int,
    @sql_handle                varbinary(64),
    @plan_handle               varbinary(64),
    @statement_start_offset    int          ,
    @statement_end_offset      int          ,
    @plan_generation_num       bigint       ,
    @database_id               smallint     ,
    @object_id                 int          ,
    @object_name               nvarchar(128),
    @query_plan                nvarchar(max)
AS
BEGIN
    SET NOCOUNT ON;

    UPDATE [snapshots].[notable_query_plan]
    SET
        database_id     = @database_id,
        object_id       = @object_id,
        object_name     = @object_name,
        query_plan      = @query_plan
    WHERE
        source_id = @source_id
        AND sql_handle = @sql_handle
        AND plan_handle = @plan_handle
        AND statement_start_offset = @statement_start_offset
        AND statement_end_offset = @statement_end_offset
        AND plan_generation_num = @plan_generation_num

END;
GO




--
-- SERVER ACTIVITY COLLECTION SET SUPPORT
--

-- os_wait_stats table
--
IF (OBJECT_ID(N'snapshots.os_wait_stats', 'U') IS NULL)
BEGIN
    PRINT 'Creating table [snapshots].[os_wait_stats]...'
    CREATE TABLE [snapshots].[os_wait_stats](
	    [wait_type]             nvarchar(45) NOT NULL,
	    [waiting_tasks_count]   bigint NOT NULL,
	    [wait_time_ms]          bigint NOT NULL,
	    [signal_wait_time_ms]   bigint NOT NULL,
	    [collection_time]       datetimeoffset(7) NOT NULL,
	    [snapshot_id]           int NOT NULL,
    );
    
    ALTER TABLE [snapshots].[os_wait_stats]  WITH CHECK ADD  CONSTRAINT [CHK_os_wait_stats_check_operator] CHECK  (([core].[fn_check_operator]([snapshot_id])=(1)))
    
    CREATE STATISTICS [STAT_os_wait_stats1] ON [snapshots].[os_wait_stats](
        [collection_time], 
        [snapshot_id], 
        [wait_type]
    )

    CREATE STATISTICS [STAT_os_wait_stats2] ON [snapshots].[os_wait_stats](
        [collection_time], 
        [wait_type]
    )

END;
GO

-- os_wait_stats.PK_os_wait_stats
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('PK_os_wait_stats', 'snapshot_id', 0, 0), 
    ('PK_os_wait_stats', 'collection_time', 0, 0), 
    ('PK_os_wait_stats', 'wait_type', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'os_wait_stats', 
    @object_type = 'PRIMARY KEY', @constraint_or_index_name = 'PK_os_wait_stats', 
    @ignore_dup_key = 0, @clustered = 1;
GO

-- os_wait_stats.IDX_os_wait_stats1
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('IDX_os_wait_stats1', 'collection_time', 0, 1), 
    ('IDX_os_wait_stats1', 'snapshot_id', 0, 0), 
    ('IDX_os_wait_stats1', 'wait_type', 1, 0), 
    ('IDX_os_wait_stats1', 'signal_wait_time_ms', 1, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'os_wait_stats', 
    @object_type = 'INDEX', @constraint_or_index_name = 'IDX_os_wait_stats1', 
    @ignore_dup_key = 0, @clustered = 0;
GO

-- os_wait_stats.IDX_os_wait_stats1
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('IDX_os_wait_stats2', 'snapshot_id', 0, 0), 
    ('IDX_os_wait_stats2', 'collection_time', 0, 0); 
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'os_wait_stats', 
    @object_type = 'INDEX', @constraint_or_index_name = 'IDX_os_wait_stats2', 
    @ignore_dup_key = 0, @clustered = 0;
GO

-- os_wait_stats.FK_os_wait_stats_snapshots_internal
IF OBJECT_ID ('snapshots.FK_os_wait_stats_snapshots_internal', 'F') IS NULL
BEGIN
    RAISERROR ('Creating foreign key [FK_os_wait_stats_snapshots_internal] on snapshots.os_wait_stats ...', 0, 1) WITH NOWAIT;
    ALTER TABLE [snapshots].[os_wait_stats] ADD CONSTRAINT [FK_os_wait_stats_snapshots_internal] FOREIGN KEY([snapshot_id])
        REFERENCES [core].[snapshots_internal] ([snapshot_id])
        ON DELETE CASCADE
END;
GO


-- os_latch_stats table
--
IF (OBJECT_ID(N'snapshots.os_latch_stats', 'U') IS NULL)
BEGIN
    PRINT 'Creating table [snapshots].[os_latch_stats]...'
    CREATE TABLE [snapshots].[os_latch_stats](
	    [latch_class]               nvarchar(45) NOT NULL,
	    [waiting_requests_count]    bigint NOT NULL,
	    [wait_time_ms]              bigint NOT NULL,
	    [collection_time]           datetimeoffset(7) NOT NULL,
	    [snapshot_id]               int NOT NULL,
    );
    
    ALTER TABLE [snapshots].[os_latch_stats] WITH CHECK ADD CONSTRAINT [CHK_os_latch_stats_check_operator] CHECK (([core].[fn_check_operator]([snapshot_id])=(1)))
END;
GO

-- os_latch_stats.PK_os_latch_stats
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('PK_os_latch_stats', 'snapshot_id', 0, 0), 
    ('PK_os_latch_stats', 'collection_time', 0, 0), 
    ('PK_os_latch_stats', 'latch_class', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'os_latch_stats', 
    @object_type = 'PRIMARY KEY', @constraint_or_index_name = 'PK_os_latch_stats', 
    @ignore_dup_key = 0, @clustered = 1;
GO

-- os_latch_stats.FK_os_latch_stats_snapshots_internal
IF OBJECT_ID ('snapshots.FK_os_latch_stats_snapshots_internal', 'F') IS NULL
BEGIN
    RAISERROR ('Creating foreign key [FK_os_latch_stats_snapshots_internal] on snapshots.os_latch_stats ...', 0, 1) WITH NOWAIT;
    ALTER TABLE [snapshots].[os_latch_stats] ADD CONSTRAINT [FK_os_latch_stats_snapshots_internal] FOREIGN KEY([snapshot_id])
        REFERENCES [core].[snapshots_internal] ([snapshot_id])
        ON DELETE CASCADE
END;
GO


IF (OBJECT_ID(N'snapshots.active_sessions_and_requests', 'U') IS NULL)
BEGIN
    PRINT 'Creating table [snapshots].[active_sessions_and_requests]...'
    CREATE TABLE [snapshots].[active_sessions_and_requests](
        [row_id]                    int NOT NULL,
	    [session_id]                smallint NOT NULL,
	    [request_id]                int NOT NULL,
	    [exec_context_id]           int NOT NULL,
	    [blocking_session_id]       smallint NULL,
	    [blocking_exec_context_id]  int NULL,
	    [scheduler_id]              int NULL,
	    [database_name]             nvarchar(128) NULL,
	    [user_id]                   int NULL,
	    [task_state]                nvarchar(10) NULL,
	    [request_status]            nvarchar(15) NULL,
	    [session_status]            nvarchar(15) NOT NULL,
	    [executing_managed_code]    bit NULL,
	    [login_time]                datetimeoffset(7) NOT NULL,
	    [is_user_process]           bit NOT NULL,
	    [host_name]                 nvarchar(20) NOT NULL,
	    [program_name]              nvarchar(50) NOT NULL,
	    [login_name]                nvarchar(30) NOT NULL,
	    [wait_type]                 nvarchar(45) NOT NULL,
	    [last_wait_type]            nvarchar(45) NOT NULL,
	    [wait_duration_ms]          bigint NOT NULL,
	    [wait_resource]             nvarchar(50) NOT NULL,
	    [resource_description]      nvarchar(140) NOT NULL,
	    [transaction_id]            bigint NULL,
	    [open_transaction_count]    int NOT NULL,
	    [transaction_isolation_level] smallint NULL,
	    [request_cpu_time]          int NULL,
	    [request_logical_reads]     bigint NULL,
	    [request_reads]             bigint NULL,
	    [request_writes]            bigint NULL,
	    [request_total_elapsed_time] int NULL,
	    [request_start_time]        datetimeoffset(7) NULL,
	    [memory_usage]              int NOT NULL,
	    [session_cpu_time]          int NOT NULL,
	    [session_reads]             bigint NOT NULL,
	    [session_writes]            bigint NOT NULL,
	    [session_logical_reads]     bigint NOT NULL,
	    [session_total_scheduled_time] int NOT NULL,
	    [session_total_elapsed_time] int NOT NULL,
	    [session_last_request_start_time] datetimeoffset(7) NOT NULL,
	    [session_last_request_end_time] datetimeoffset(7) NULL,
	    [open_resultsets]           int NULL,
	    [session_row_count]         bigint NOT NULL,
	    [prev_error]                int NOT NULL,
	    [pending_io_count]          int NULL,
	    [command]                   nvarchar(16) NULL,
	    [plan_handle]               varbinary(64) NULL,
	    [sql_handle]                varbinary(64) NULL,
	    [statement_start_offset]    int NULL,
	    [statement_end_offset]      int NULL,
        [collection_time]           datetimeoffset(7) NOT NULL,
        [snapshot_id]               int NOT NULL,
        [is_blocking]               bit NOT NULL,
    );

    ALTER TABLE [snapshots].[active_sessions_and_requests] WITH CHECK ADD CONSTRAINT [CHK_active_sessions_and_requests_check_operator] CHECK (([core].[fn_check_operator]([snapshot_id])=(1)))
END;
GO

-- active_sessions_and_requests.PK_active_sessions_and_requests
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('PK_active_sessions_and_requests', 'snapshot_id', 0, 0), 
    ('PK_active_sessions_and_requests', 'collection_time', 0, 0), 
    ('PK_active_sessions_and_requests', 'row_id', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'active_sessions_and_requests', 
    @object_type = 'PRIMARY KEY', @constraint_or_index_name = 'PK_active_sessions_and_requests', 
    @ignore_dup_key = 0, @clustered = 1;
GO

-- active_sessions_and_requests.IDX_blocking_session_id
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('IDX_blocking_session_id', 'blocking_session_id', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'active_sessions_and_requests', 
    @object_type = 'INDEX', @constraint_or_index_name = 'IDX_blocking_session_id', 
    @ignore_dup_key = 0, @clustered = 0;
GO

-- active_sessions_and_requests.IDX_collection_time
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('IDX_collection_time', 'collection_time', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'active_sessions_and_requests', 
    @object_type = 'INDEX', @constraint_or_index_name = 'IDX_collection_time', 
    @ignore_dup_key = 0, @clustered = 0;
GO

-- active_sessions_and_requests.FK_active_sessions_and_requests_snapshots_internal
IF OBJECT_ID ('snapshots.FK_active_sessions_and_requests_snapshots_internal', 'F') IS NULL
BEGIN
    RAISERROR ('Creating foreign key [FK_active_sessions_and_requests_snapshots_internal] on snapshots.active_sessions_and_requests ...', 0, 1) WITH NOWAIT;
    ALTER TABLE [snapshots].[active_sessions_and_requests] ADD CONSTRAINT [FK_active_sessions_and_requests_snapshots_internal] FOREIGN KEY([snapshot_id])
        REFERENCES [core].[snapshots_internal] ([snapshot_id])
        ON DELETE CASCADE
END;
GO


IF (OBJECT_ID(N'snapshots.os_schedulers', 'U') IS NULL)
BEGIN
    PRINT 'Creating table [snapshots].[os_schedulers]...'
    CREATE TABLE [snapshots].[os_schedulers] (
	    [parent_node_id]			int	NOT NULL,
	    [scheduler_id]				int	NOT NULL,
	    [cpu_id]					int NOT NULL,
	    [status]					nvarchar(60) NOT NULL,
	    [is_idle]					bit NOT NULL,
	    [preemptive_switches_count]	int NOT NULL,
	    [context_switches_count]	int NOT NULL,
	    [yield_count]				int NOT NULL,
	    [current_tasks_count]		int NOT NULL,
	    [runnable_tasks_count]		int NOT NULL,
	    [work_queue_count]			bigint NOT NULL,
	    [pending_disk_io_count]		int NOT NULL,
        [collection_time]           datetimeoffset(7) NOT NULL,
        [snapshot_id]               int NOT NULL,
    );

    ALTER TABLE [snapshots].[os_schedulers] WITH CHECK ADD CONSTRAINT [CHK_os_schedulers_check_operator] CHECK (([core].[fn_check_operator]([snapshot_id])=(1)))
END;
GO

-- os_schedulers.PK_os_schedulers
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('PK_os_schedulers', 'snapshot_id', 0, 0), 
    ('PK_os_schedulers', 'collection_time', 0, 0), 
    ('PK_os_schedulers', 'scheduler_id', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'os_schedulers', 
    @object_type = 'PRIMARY KEY', @constraint_or_index_name = 'PK_os_schedulers', 
    @ignore_dup_key = 0, @clustered = 1;
GO

-- os_schedulers.FK_os_schedulers_snapshots_internal
IF OBJECT_ID ('snapshots.FK_os_schedulers_snapshots_internal', 'F') IS NULL
BEGIN
    RAISERROR ('Creating foreign key [FK_os_schedulers_snapshots_internal] on snapshots.os_schedulers ...', 0, 1) WITH NOWAIT;
    ALTER TABLE [snapshots].[os_schedulers] ADD CONSTRAINT [FK_os_schedulers_snapshots_internal] FOREIGN KEY([snapshot_id])
        REFERENCES [core].[snapshots_internal] ([snapshot_id])
        ON DELETE CASCADE
END;
GO


IF (OBJECT_ID(N'snapshots.os_memory_nodes', 'U') IS NULL)
BEGIN
    PRINT 'Creating table [snapshots].[os_memory_nodes]...'
    CREATE TABLE [snapshots].[os_memory_nodes] (
        [memory_node_id]                        smallint NOT NULL, 
        [virtual_address_space_reserved_kb]     bigint NOT NULL, 
        [virtual_address_space_committed_kb]    bigint NOT NULL, 
        [locked_page_allocations_kb]            bigint NOT NULL, 
        [single_pages_kb]                       bigint NOT NULL, 
        [multi_pages_kb]                        bigint NOT NULL, 
        [shared_memory_reserved_kb]             bigint NOT NULL, 
        [shared_memory_committed_kb]            bigint NOT NULL,
        [collection_time]                       datetimeoffset(7) NOT NULL,
        [snapshot_id]                           int NOT NULL,
    );

    ALTER TABLE [snapshots].[os_memory_nodes] WITH CHECK ADD CONSTRAINT [CHK_os_memory_nodes_check_operator] CHECK (([core].[fn_check_operator]([snapshot_id])=(1)))
END;
GO

-- os_memory_nodes.PK_os_memory_nodes
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('PK_os_memory_nodes', 'snapshot_id', 0, 0), 
    ('PK_os_memory_nodes', 'collection_time', 0, 0), 
    ('PK_os_memory_nodes', 'memory_node_id', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'os_memory_nodes', 
    @object_type = 'PRIMARY KEY', @constraint_or_index_name = 'PK_os_memory_nodes', 
    @ignore_dup_key = 0, @clustered = 1;
GO

-- os_memory_nodes.FK_os_memory_nodes_snapshots_internal
IF OBJECT_ID ('snapshots.FK_os_memory_nodes_snapshots_internal', 'F') IS NULL
BEGIN
    RAISERROR ('Creating foreign key [FK_os_memory_nodes_snapshots_internal] on snapshots.os_memory_nodes ...', 0, 1) WITH NOWAIT;
    ALTER TABLE [snapshots].[os_memory_nodes] ADD CONSTRAINT [FK_os_memory_nodes_snapshots_internal] FOREIGN KEY([snapshot_id])
        REFERENCES [core].[snapshots_internal] ([snapshot_id])
        ON DELETE CASCADE
END;
GO


-- NOTE: Used by System Activity collection set up to CTP6 Refresh/RC0. Not used in RTM and later. 
IF (OBJECT_ID(N'snapshots.os_process_memory', 'U') IS NULL)
BEGIN
    PRINT 'Creating table [snapshots].[os_process_memory]...'
    CREATE TABLE [snapshots].[os_process_memory] (
        [physical_memory_in_use_kb]              bigint NOT NULL,
        [large_page_allocations_kb]              bigint NOT NULL, 
        [locked_page_allocations_kb]             bigint NOT NULL, 
        [total_virtual_address_space_kb]         bigint NOT NULL,
        [virtual_address_space_reserved_kb]      bigint NOT NULL,
        [virtual_address_space_committed_kb]     bigint NOT NULL,
        [virtual_address_space_available_kb]     bigint NOT NULL,
        [page_fault_count]                       bigint NOT NULL,
        [memory_utilization_percentage]          int NOT NULL,
        [available_commit_limit_kb]              bigint NOT NULL,
        [process_physical_memory_low]            bit NOT NULL,
        [process_virtual_memory_low]             bit NOT NULL,
        [collection_time]                        datetimeoffset(7) NOT NULL,
        [snapshot_id]                            int NOT NULL,
    );

    ALTER TABLE [snapshots].[os_process_memory] WITH CHECK ADD CONSTRAINT [CHK_os_process_memory_check_operator] CHECK (([core].[fn_check_operator]([snapshot_id])=(1)))
END;
GO

-- os_process_memory.PK_os_process_memory
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('PK_os_process_memory', 'snapshot_id', 0, 0), 
    ('PK_os_process_memory', 'collection_time', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'os_process_memory', 
    @object_type = 'PRIMARY KEY', @constraint_or_index_name = 'PK_os_process_memory', 
    @ignore_dup_key = 0, @clustered = 1;
GO

-- os_process_memory.FK_os_process_memory_snapshots_internal
IF OBJECT_ID ('snapshots.FK_os_process_memory_snapshots_internal', 'F') IS NULL
BEGIN
    RAISERROR ('Creating foreign key [FK_os_process_memory_snapshots_internal] on snapshots.os_process_memory ...', 0, 1) WITH NOWAIT;
    ALTER TABLE [snapshots].[os_process_memory] ADD CONSTRAINT [FK_os_process_memory_snapshots_internal] FOREIGN KEY([snapshot_id])
        REFERENCES [core].[snapshots_internal] ([snapshot_id])
        ON DELETE CASCADE
END;
GO


IF (OBJECT_ID(N'snapshots.sql_process_and_system_memory', 'U') IS NULL)
BEGIN
    PRINT 'Creating table [snapshots].[sql_process_and_system_memory]...';
    CREATE TABLE [snapshots].[sql_process_and_system_memory](
	    [sql_physical_memory_in_use_kb]             bigint NOT NULL,
	    [sql_large_page_allocations_kb]             bigint NOT NULL,
	    [sql_locked_page_allocations_kb]            bigint NOT NULL,
	    [sql_total_virtual_address_space_kb]        bigint NOT NULL,
	    [sql_virtual_address_space_reserved_kb]     bigint NOT NULL,
	    [sql_virtual_address_space_committed_kb]    bigint NOT NULL,
	    [sql_virtual_address_space_available_kb]    bigint NOT NULL,
	    [sql_page_fault_count]                      bigint NOT NULL,
	    [sql_memory_utilization_percentage]         int NOT NULL,
	    [sql_available_commit_limit_kb]             bigint NOT NULL,
	    [sql_process_physical_memory_low]           bit NOT NULL,
	    [sql_process_virtual_memory_low]            bit NOT NULL,
	    [system_total_physical_memory_kb]           bigint NOT NULL,
	    [system_available_physical_memory_kb]       bigint NOT NULL,
	    [system_total_page_file_kb]                 bigint NOT NULL,
	    [system_available_page_file_kb]             bigint NOT NULL,
	    [system_cache_kb]                           bigint NOT NULL,
	    [system_kernel_paged_pool_kb]               bigint NOT NULL,
	    [system_kernel_nonpaged_pool_kb]            bigint NOT NULL,
	    [system_high_memory_signal_state]           bit NOT NULL,
	    [system_low_memory_signal_state]            bit NOT NULL,
	    [bpool_commit_target]                       bigint NOT NULL,
	    [bpool_committed]                           bigint NOT NULL,
	    [bpool_visible]                             bigint NOT NULL, 
	    [collection_time]                           datetimeoffset(7) NOT NULL,
	    [snapshot_id]                               int NOT NULL,
    );

    ALTER TABLE [snapshots].[sql_process_and_system_memory] WITH CHECK ADD CONSTRAINT [CHK_sql_process_and_system_memory_check_operator] CHECK (([core].[fn_check_operator]([snapshot_id])=(1)));
END;
GO

-- sql_process_and_system_memory.PK_sql_process_and_system_memory
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('PK_sql_process_and_system_memory', 'snapshot_id', 0, 0), 
    ('PK_sql_process_and_system_memory', 'collection_time', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'sql_process_and_system_memory', 
    @object_type = 'PRIMARY KEY', @constraint_or_index_name = 'PK_sql_process_and_system_memory', 
    @ignore_dup_key = 0, @clustered = 1;
GO



-- sql_process_and_system_memory.FK_sql_process_and_system_memory_internal
IF OBJECT_ID ('snapshots.FK_sql_process_and_system_memory_internal', 'F') IS NULL
BEGIN
    RAISERROR ('Creating foreign key [FK_sql_process_and_system_memory_internal] on snapshots.sql_process_and_system_memory ...', 0, 1) WITH NOWAIT;
    ALTER TABLE [snapshots].[sql_process_and_system_memory] ADD CONSTRAINT [FK_sql_process_and_system_memory_internal] FOREIGN KEY([snapshot_id])
        REFERENCES [core].[snapshots_internal] ([snapshot_id])
        ON DELETE CASCADE;
END;
GO


IF (OBJECT_ID(N'snapshots.os_memory_clerks', 'U') IS NULL)
BEGIN
    PRINT 'Creating table [snapshots].[os_memory_clerks]...'
    CREATE TABLE [snapshots].[os_memory_clerks] (
        [type]                                  nvarchar(60),
        [memory_node_id]                        smallint,
        [single_pages_kb]                       bigint,
        [multi_pages_kb]                        bigint,
        [virtual_memory_reserved_kb]            bigint,
        [virtual_memory_committed_kb]           bigint,
        [awe_allocated_kb]                      bigint,
        [shared_memory_reserved_kb]             bigint,
        [shared_memory_committed_kb]            bigint,
        [collection_time]                       datetimeoffset(7) NOT NULL,
        [snapshot_id]                           int NOT NULL
    ) ON [PRIMARY]

    ALTER TABLE [snapshots].[os_memory_clerks] WITH CHECK ADD CONSTRAINT [CHK_os_memory_clerks_check_operator] CHECK (([core].[fn_check_operator]([snapshot_id])=(1)))
END
GO

-- os_memory_clerks.CIDX_os_memory_clerks
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('CIDX_os_memory_clerks', 'snapshot_id', 0, 0), 
    ('CIDX_os_memory_clerks', 'collection_time', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'os_memory_clerks', 
    @object_type = 'INDEX', @constraint_or_index_name = 'CIDX_os_memory_clerks', 
    @ignore_dup_key = 0, @clustered = 1;
GO

-- os_memory_clerks.FK_os_memory_clerks_snapshots_internal
IF OBJECT_ID ('snapshots.FK_os_memory_clerks_snapshots_internal', 'F') IS NULL
BEGIN
    RAISERROR ('Creating foreign key [FK_os_memory_clerks_snapshots_internal] on snapshots.os_memory_clerks ...', 0, 1) WITH NOWAIT;
    ALTER TABLE [snapshots].[os_memory_clerks] ADD CONSTRAINT [FK_os_memory_clerks_snapshots_internal] FOREIGN KEY([snapshot_id])
        REFERENCES [core].[snapshots_internal] ([snapshot_id])
        ON DELETE CASCADE
END;
GO


--
-- QUERY ACTIVITY COLLECTION SET SUPPORT
--

IF (OBJECT_ID(N'snapshots.query_stats', 'U') IS NULL)
BEGIN
    PRINT 'Creating table [snapshots].[query_stats]...'
    CREATE TABLE [snapshots].[query_stats] (
  	     [sql_handle]			    varbinary(64) NOT NULL,
	     [statement_start_offset]   int NOT NULL,
	     [statement_end_offset]		int NOT NULL,
	     [plan_generation_num]		bigint NOT NULL,
	     [plan_handle]				varbinary(64) NOT NULL,
	     [creation_time]			datetimeoffset(7) NOT NULL,
	     [last_execution_time]		datetimeoffset(7) NOT NULL,
	     [execution_count]			bigint NOT NULL,
	     [snapshot_execution_count]	bigint NULL,
	     [total_worker_time]		bigint NOT NULL,
	     [snapshot_worker_time]		bigint NOT NULL,
	     [min_worker_time]			bigint NULL,        -- NULLable b/c we can't calc min for queries that are still in-progress 
	     [max_worker_time]			bigint NOT NULL,
	     [total_physical_reads]		bigint NOT NULL,
	     [snapshot_physical_reads]	bigint NOT NULL,
	     [min_physical_reads]		bigint NULL,        -- NULLable b/c we can't calc min for queries that are still in-progress 
	     [max_physical_reads]		bigint NOT NULL,
	     [total_logical_writes]		bigint NOT NULL,
	     [snapshot_logical_writes]	bigint NOT NULL,
	     [min_logical_writes]		bigint NULL,        -- NULLable b/c we can't calc min for queries that are still in-progress 
	     [max_logical_writes]		bigint NOT NULL,
	     [total_logical_reads]		bigint NOT NULL,
	     [snapshot_logical_reads]	bigint NOT NULL,
	     [min_logical_reads]	    bigint NULL,        -- NULLable b/c we can't calc min for queries that are still in-progress 
	     [max_logical_reads]	    bigint NOT NULL,
	     [total_clr_time]		    bigint NULL,        -- NULLable b/c dm_exec_requests doesn't expose this stat
	     [snapshot_clr_time]		bigint NULL,        -- NULLable b/c dm_exec_requests doesn't expose this stat
	     [min_clr_time]			    bigint NULL,        -- NULLable b/c we can't calc min for queries that are still in-progress 
	     [max_clr_time]			    bigint NULL,        -- NULLable b/c dm_exec_requests doesn't expose this stat 
	     [total_elapsed_time]	    bigint NOT NULL,
	     [snapshot_elapsed_time]	bigint NOT NULL,
	     [min_elapsed_time]		    bigint NULL,        -- NULLable b/c we can't calc min for queries that are still in-progress 
	     [max_elapsed_time]		    bigint NOT NULL,
         [collection_time]          datetimeoffset(7) NOT NULL,
         [snapshot_id]              int NOT NULL

    ) ON [PRIMARY]

    ALTER TABLE [snapshots].[query_stats] WITH CHECK ADD CONSTRAINT [CHK_query_stats_check_operator] CHECK (([core].[fn_check_operator]([snapshot_id])=(1)))
END
GO

-- query_stats.PK_query_stats
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('CIDX_query_stats', 'snapshot_id', 0, 0), 
    ('CIDX_query_stats', 'collection_time', 0, 0), 
    ('CIDX_query_stats', 'sql_handle', 0, 0), 
    ('CIDX_query_stats', 'statement_start_offset', 0, 0), 
    ('CIDX_query_stats', 'statement_end_offset', 0, 0), 
    ('CIDX_query_stats', 'plan_handle', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'query_stats', 
    @object_type = 'INDEX', @constraint_or_index_name = 'CIDX_query_stats', 
    @ignore_dup_key = 0, @clustered = 1;
GO

-- query_stats.FK_query_stats_snapshots_internal
IF OBJECT_ID ('snapshots.FK_query_stats_snapshots_internal', 'F') IS NULL
BEGIN
    RAISERROR ('Creating foreign key [FK_query_stats_snapshots_internal] on snapshots.query_stats ...', 0, 1) WITH NOWAIT;
    ALTER TABLE [snapshots].[query_stats] ADD CONSTRAINT [FK_query_stats_snapshots_internal] FOREIGN KEY([snapshot_id])
        REFERENCES [core].[snapshots_internal] ([snapshot_id])
        ON DELETE CASCADE
END;
GO


IF (NOT OBJECT_ID(N'snapshots.distinct_query_stats', 'V') IS NULL)
BEGIN
    PRINT 'Dropping view [snapshots].[distinct_query_stats]...'
    DROP VIEW [snapshots].[distinct_query_stats]
END
GO

PRINT 'Creating view [snapshots].[distinct_query_stats]...'
GO
CREATE VIEW [snapshots].[distinct_query_stats]
AS 
    SELECT 
        dqth.distinct_query_hash,
        SUM(qs.execution_count) AS execution_count,
        SUM(qs.total_worker_time) AS total_worker_time,
        SUM(qs.total_physical_reads) AS total_physical_reads,
        SUM(qs.total_logical_reads) AS total_logical_reads,
        SUM(qs.total_logical_writes) AS total_logical_writes,
        SUM(qs.total_clr_time) AS total_clr_time,
        SUM(qs.total_elapsed_time) AS total_elapsed_time
    FROM
        [snapshots].[query_stats] qs
        JOIN [core].[snapshots_internal] s ON (s.snapshot_id = qs.snapshot_id)
        JOIN [snapshots].[distinct_query_to_handle] dqth ON (s.source_id = dqth.source_id AND qs.sql_handle = dqth.sql_handle)
    GROUP BY
        dqth.distinct_query_hash
GO



--
-- DISK USAGE COLLECTION SET SUPPORT
--

-- disk_usage table
--
IF (OBJECT_ID(N'snapshots.disk_usage', 'U') IS NULL)
BEGIN
    PRINT 'Creating table [snapshots].[disk_usage]...'
    CREATE TABLE [snapshots].[disk_usage](
	    [dbsize] [bigint] NULL,
	    [logsize] [bigint] NULL,
	    [ftsize] [bigint] NULL,
	    [reservedpages] [bigint] NULL,
	    [usedpages] [bigint] NULL,
	    [pages] [bigint] NULL,
	    [database_name] [nvarchar](128) NOT NULL,
	    [collection_time] datetimeoffset(7) NOT NULL,
	    [snapshot_id] [int] NOT NULL, 
    ) ON [PRIMARY]

    ALTER TABLE [snapshots].[disk_usage]  WITH CHECK ADD  CONSTRAINT [CHK_disk_usage_check_operator] CHECK (([core].[fn_check_operator]([snapshot_id])=(1)))
END
GO

-- disk_usage.PK_disk_usage
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('PK_disk_usage', 'snapshot_id', 0, 0), 
    ('PK_disk_usage', 'collection_time', 0, 0), 
    ('PK_disk_usage', 'database_name', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'disk_usage', 
    @object_type = 'PRIMARY KEY', @constraint_or_index_name = 'PK_disk_usage', 
    @ignore_dup_key = 0, @clustered = 1;
GO

-- disk_usage.FK_disk_usage_snapshots_internal
IF OBJECT_ID ('snapshots.FK_disk_usage_snapshots_internal', 'F') IS NULL
BEGIN
    RAISERROR ('Creating foreign key [FK_disk_usage_snapshots_internal] on snapshots.disk_usage ...', 0, 1) WITH NOWAIT;
    ALTER TABLE [snapshots].[disk_usage]  WITH CHECK ADD  CONSTRAINT [FK_disk_usage_snapshots_internal] FOREIGN KEY([snapshot_id])
        REFERENCES [core].[snapshots_internal] ([snapshot_id])
        ON DELETE CASCADE
END;
GO


-- log_usage table
--
IF (OBJECT_ID(N'snapshots.log_usage', 'U') IS NULL)
BEGIN
    PRINT 'Creating table [snapshots].[log_usage]...'
    CREATE TABLE [snapshots].[log_usage](
	    [database_name] [nvarchar](128) NOT NULL,
	    [log_size_mb] [float] NULL,
	    [log_space_used] [float] NULL,
	    [status] [int] NULL,
	    [collection_time] datetimeoffset(7) NOT NULL,
	    [snapshot_id] [int] NOT NULL, 
    ) ON [PRIMARY]

    ALTER TABLE [snapshots].[log_usage] WITH CHECK ADD CONSTRAINT [CHK_log_usage_check_operator] CHECK (([core].[fn_check_operator]([snapshot_id])=(1)))
END
GO

-- log_usage.PK_log_usage
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('PK_log_usage', 'snapshot_id', 0, 0), 
    ('PK_log_usage', 'collection_time', 0, 0), 
    ('PK_log_usage', 'database_name', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'log_usage', 
    @object_type = 'PRIMARY KEY', @constraint_or_index_name = 'PK_log_usage', 
    @ignore_dup_key = 0, @clustered = 1;
GO

-- log_usage.FK_log_usage_snapshots_internal
IF OBJECT_ID ('snapshots.FK_log_usage_snapshots_internal', 'F') IS NULL
BEGIN
    RAISERROR ('Creating foreign key [FK_log_usage_snapshots_internal] on snapshots.log_usage ...', 0, 1) WITH NOWAIT;
    ALTER TABLE [snapshots].[log_usage] WITH CHECK ADD CONSTRAINT [FK_log_usage_snapshots_internal] FOREIGN KEY([snapshot_id])
        REFERENCES [core].[snapshots_internal] ([snapshot_id])
        ON DELETE CASCADE
END;
GO


-- io_virtual_file_stats table
--
IF (OBJECT_ID(N'snapshots.io_virtual_file_stats', 'U') IS NULL)
BEGIN
    PRINT 'Creating table [snapshots].[io_virtual_file_stats]...'
    CREATE TABLE [snapshots].[io_virtual_file_stats] (
	    [database_name] [nvarchar](128) NOT NULL,
	    [database_id] [int] NOT NULL,
	    [logical_file_name] [nvarchar](128) NOT NULL,
	    [file_id] [int] NOT NULL,
	    [type_desc] [nvarchar](60) NULL,
	    [logical_disk] [nvarchar](255) NOT NULL,
	    [num_of_reads] [bigint] NULL,
	    [num_of_bytes_read] [bigint] NULL,
	    [io_stall_read_ms] [bigint] NULL,
	    [num_of_writes] [bigint] NULL,
	    [num_of_bytes_written] [bigint] NULL,
	    [io_stall_write_ms] [bigint] NULL,
	    [size_on_disk_bytes] [bigint] NULL,
	    [collection_time] [datetimeoffset](7) NOT NULL,
	    [snapshot_id] [int] NOT NULL, 
    	
    ) ON [PRIMARY]

    ALTER TABLE [snapshots].[io_virtual_file_stats] WITH CHECK ADD CONSTRAINT [CHK_io_virtual_file_stats_check_operator] CHECK (([core].[fn_check_operator]([snapshot_id])=(1)))
END
GO

-- io_virtual_file_stats.PK_io_virtual_file_stats
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('PK_io_virtual_file_stats', 'snapshot_id', 0, 0), 
    ('PK_io_virtual_file_stats', 'collection_time', 0, 0), 
    ('PK_io_virtual_file_stats', 'logical_disk', 0, 0), 
    ('PK_io_virtual_file_stats', 'database_name', 0, 0), 
    ('PK_io_virtual_file_stats', 'file_id', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'io_virtual_file_stats', 
    @object_type = 'PRIMARY KEY', @constraint_or_index_name = 'PK_io_virtual_file_stats', 
    @ignore_dup_key = 0, @clustered = 1;
GO

-- io_virtual_file_stats.FK_io_virtual_file_stats
IF OBJECT_ID ('snapshots.FK_io_virtual_file_stats', 'F') IS NULL
BEGIN
    RAISERROR ('Creating foreign key [FK_io_virtual_file_stats] on snapshots.io_virtual_file_stats ...', 0, 1) WITH NOWAIT;
    ALTER TABLE [snapshots].[io_virtual_file_stats] WITH CHECK ADD CONSTRAINT [FK_io_virtual_file_stats] FOREIGN KEY([snapshot_id])
        REFERENCES [core].[snapshots_internal] ([snapshot_id])
        ON DELETE CASCADE
END;
GO



/**********************************************************************/
/* REPORTING STORED PROCEDURES                                       */
/**********************************************************************/

--
-- snapshots.rpt_snapshot_times
--  Returns all snapshot times for a given collection set.  
--  Used by (nearly) all reports to return data for the timeline chart that sits at the top of each report. 
--
-- Parameters: 
--    @ServerName - SQL Server instance name
--    @EndTime - End of the user-selected time window (UTC)
--    @WindowSize - Number of minutes in the time window 
--    @CollectionSetUid - GUID of the collection set with the snapshot_times that the report cares about
--
IF (NOT OBJECT_ID(N'snapshots.rpt_snapshot_times', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[rpt_snapshot_times] ...'
    DROP PROCEDURE [snapshots].[rpt_snapshot_times]
END
GO 

PRINT 'Creating procedure [snapshots].[rpt_snapshot_times] ...'
GO
CREATE PROC [snapshots].[rpt_snapshot_times]
    @ServerName sysname, 
    @EndTime datetime, 
    @WindowSize int, 
    @CollectionSetUid uniqueidentifier
AS
BEGIN
    DECLARE @end_time_internal datetimeoffset(7)
    SET @end_time_internal = TODATETIMEOFFSET (@EndTime, '+00:00')

    -- Get the time of the earliest and latest snapshots for this collection set
    DECLARE @min_snapshot_time datetimeoffset(7)
    DECLARE @max_snapshot_time datetimeoffset(7)
    DECLARE @total_data_collection_window int
    SELECT @min_snapshot_time = MIN (snapshot_time), @max_snapshot_time = MAX (snapshot_time)
    FROM core.snapshots 
    WHERE instance_name = @ServerName
        AND collection_set_uid = @CollectionSetUid
    IF @min_snapshot_time IS NULL SET @min_snapshot_time = SYSDATETIMEOFFSET()
    IF @max_snapshot_time IS NULL SET @max_snapshot_time = SYSDATETIMEOFFSET()
    SET @total_data_collection_window = DATEDIFF (minute, @min_snapshot_time, @max_snapshot_time)

    -- First return all snapshot_time values for this collection set
    SELECT DISTINCT 
        CONVERT (datetime, SWITCHOFFSET (CAST (snapshot_time AS datetimeoffset(7)), '+00:00')) AS snapshot_time,
        1 AS value, 
        'AllSnapshotTimes' AS series_name
    FROM core.snapshots
    WHERE instance_name = @ServerName
        AND collection_set_uid = @CollectionSetUid
    UNION ALL
     
    -- Then return the snapshot_time values that are in the selected time window, with a different series label. 
    SELECT DISTINCT 
        CONVERT (datetime, SWITCHOFFSET (CAST (snapshot_time AS datetimeoffset(7)), '+00:00')) AS snapshot_time,
        0.8 AS value, 
        'SelectedSnapshotTimes' AS series_name
    FROM core.snapshots
    WHERE instance_name = @ServerName
        AND collection_set_uid = @CollectionSetUid
        AND snapshot_time BETWEEN DATEADD (minute, -1 * @WindowSize, @end_time_internal) AND @end_time_internal 
    UNION ALL 
    SELECT DISTINCT 
        DATEADD (millisecond, 10, CONVERT (datetime, SWITCHOFFSET (CAST (snapshot_time AS datetimeoffset(7)), '+00:00'))) AS snapshot_time,
        1.2 AS value, 
        'SelectedSnapshotTimes' AS series_name
    FROM core.snapshots
    WHERE instance_name = @ServerName
        AND collection_set_uid = @CollectionSetUid
        AND snapshot_time BETWEEN DATEADD (minute, -1 * @WindowSize, @end_time_internal) AND @end_time_internal 

    -- Return a "fake" data point (will not be plotted) so that the timeline always extends to the current time
    UNION ALL 
        SELECT GETUTCDATE() AS snapshot_time,
        -1 AS value, 
        'Formatting' AS in_selected_time_window

    -- Order is important here since points are plotted in the order in which they are returned from the query. 
    -- The "SelectedSnapshotTimes" series must be drawn on top of the "AllSnapshotTimes" series, so we return it 
    -- last. 
    ORDER BY series_name ASC, snapshot_time
END
GO


--
-- snapshots.rpt_interval_collection_times
--  Helper proc used by other procs that need to return data from N evenly-spaced intervals within a larger user-specified time window
--  Sample usage: 
--  
--      CREATE TABLE #intervals (
--          interval_time_id        int, 
--          interval_start_time     datetimeoffset(7),
--          interval_end_time       datetimeoffset(7),
--          interval_id             int, 
--          first_collection_time   datetimeoffset(7), 
--          last_collection_time    datetimeoffset(7), 
--          first_snapshot_id       int,
--          last_snapshot_id        int, 
--          -- the following columns may be ignored if @include_snapshot_detail = 0
--          source_id               int,                
--          snapshot_id             int,
--          collection_time         datetimeoffset(7),
--          collection_time_id      int
--      )
--      -- GUID 49268954-... is the Server Activity CS
--      INSERT INTO #intervals
--      EXEC [snapshots].[rpt_interval_collection_times] 
--          @ServerName, @EndTime, @WindowSize, 'snapshots.os_memory_clerks', '49268954-4FD4-4EB6-AA04-CD59D9BB5714', 40
--      
--      SELECT ... 
--      FROM snapshots.dm_os_memory_clerks AS mc 
--      JOIN #intervals AS col ON mc.collection_time = col.last_collection_time AND mc.snapshot_id = col.last_snapshot_id
--      WHERE ...
--
--
-- Parameters: 
--    @ServerName - SQL Server instance name
--    @EndTime - End of the user-selected time window (UTC)
--    @WindowSize - Number of minutes in the time window 
--    @TargetCollectionTable - Name of the table from which to harvest the collection_time values. Must have a datetimeoffset [collection_time] 
--        column and be in the [snapshots] or [custom_snapshots] schema. 
--    @CollectionSetUid - GUID of the collection set that populates @TargetCollectionTable
--    @interval_count int - Number of time intervals to divide the time window up into (default 40)
--    @include_snapshot_detail - 0 if the caller only wants 
--
IF (NOT OBJECT_ID(N'snapshots.rpt_interval_collection_times', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[rpt_interval_collection_times] ...'
    DROP PROCEDURE [snapshots].[rpt_interval_collection_times]
END
GO 

PRINT 'Creating procedure [snapshots].[rpt_interval_collection_times] ...'
GO
CREATE PROCEDURE [snapshots].[rpt_interval_collection_times]
    @ServerName sysname,
    @EndTime datetime = NULL,
    @WindowSize int = NULL, 
    @TargetCollectionTable sysname, 
    @CollectionSetUid varchar(64), 
    @interval_count int = 40, 
    @include_snapshot_detail bit = 0
AS
BEGIN
    SET NOCOUNT ON;
    
    DECLARE @start_time_internal datetimeoffset(7);
    DECLARE @end_time_internal datetimeoffset(7);
    
    -- Start time should be passed in as a UTC datetime
    IF (@EndTime IS NOT NULL)
    BEGIN
        -- Assumed time zone offset for this conversion is +00:00 (datetime must be passed in as UTC)
        SET @end_time_internal = CAST (@EndTime AS datetimeoffset(7));
    END
    ELSE BEGIN
        SELECT @end_time_internal = MAX(snapshot_time)
        FROM core.snapshots 
        WHERE instance_name = @ServerName AND collection_set_uid = @CollectionSetUid
    END
    SET @start_time_internal = DATEADD (minute, -1 * @WindowSize, @end_time_internal);
    
    -- Get the earliest and latest snapshot_id values that could contain data for the selected time interval. 
    -- This will allow a more efficient query plan. 
    DECLARE @start_snapshot_id int;
    DECLARE @end_snapshot_id int;
    
    SELECT @start_snapshot_id = MIN (t.snapshot_id)
    FROM
    (
        SELECT TOP 2 s.snapshot_id
        FROM core.snapshots AS s 
        WHERE s.instance_name = @ServerName AND s.collection_set_uid = @CollectionSetUid
            AND s.snapshot_time < @start_time_internal
        ORDER BY s.snapshot_id DESC
    ) AS t
    
    SELECT @end_snapshot_id = MAX (t.snapshot_id) 
    FROM 
    (
        SELECT TOP 2 snapshot_id
        FROM core.snapshots AS s 
        WHERE s.instance_name = @ServerName AND s.collection_set_uid = @CollectionSetUid
            AND s.snapshot_time >= @end_time_internal
        ORDER BY s.snapshot_id ASC
    ) AS t
    
    IF @start_snapshot_id IS NULL SELECT @start_snapshot_id = MIN (snapshot_id) FROM core.snapshots
    IF @end_snapshot_id IS NULL SELECT @end_snapshot_id = MAX (snapshot_id) FROM core.snapshots
    
    -- Divide the time window up into N equal intervals.  
    -- First, calculate the duration of one interval, in minutes. 
    DECLARE @group_interval_min int
    SET @group_interval_min = ROUND (DATEDIFF (second, @start_time_internal, @end_time_internal) / 60.0 / @interval_count, 0)
    IF @group_interval_min = 0 SET @group_interval_min = 1
        
    IF (ISNULL (PARSENAME (@TargetCollectionTable, 2), 'snapshots') IN ('snapshots', 'custom_snapshots'))
    BEGIN
        /*
        Some explanation of the expressions used in the query below: 
        
        DATEDIFF (minute, ''20000101'', dataTable.collection_time) / @group_interval_min AS interval_time_id
            - @group_interval_min is the length of one time interval (one Nth of the selected time window), in minutes
            - "DATEDIFF (minute, ''20000101'', dataTable.collection_time)" converts each collection time into an integer (the # of minutes since a fixed reference date)
            - This value is divided by @group_interval_min to get an integer "interval ID".  The query uses this in the GROUP BY clause to group together all collection 
              times that fall within the same time interval.

        DATEADD (minute, (<<interval_id_expression>> * @group_interval_min, ''20000101'') AS interval_start_time
            - This uses (interval number) * (minutes/interval) + (reference date) to generate the time interval's start time 
            - The next column ([interval_end_time]) is the same as the above, except the time is calculated for the following interval ID (an interval's end time is 
              also the start time for the following time interval)
        */

        -- Get the collection times that fall within the specified time window, and compute the time interval ID for each collection time
        CREATE TABLE #snapshots (
            interval_time_id int, 
            interval_start_time datetimeoffset(7), 
            interval_end_time datetimeoffset(7), 
            interval_id int, 
            collection_time datetimeoffset(7), 
            source_id int, 
            snapshot_id int
        )

        -- Dynamic SQL will re-evaluate object permissions -- the current user must have SELECT permission on the target table. 
        DECLARE @sql nvarchar(4000)
        SET @sql = '
        INSERT INTO #snapshots 
        SELECT DISTINCT 
            DATEDIFF (minute, ''20000101'', dataTable.collection_time) / @group_interval_min AS interval_time_id, 
            DATEADD (minute, (DATEDIFF (minute, ''20000101'', dataTable.collection_time) / @group_interval_min) * @group_interval_min, ''20000101'') AS interval_start_time, 
            DATEADD (minute, (DATEDIFF (minute, ''20000101'', dataTable.collection_time) / @group_interval_min + 1) * @group_interval_min, ''20000101'') AS interval_end_time, 
            DENSE_RANK() OVER (ORDER BY DATEDIFF (minute, ''20000101'', dataTable.collection_time) / @group_interval_min) AS interval_id,  
            dataTable.collection_time, s.source_id, s.snapshot_id 
        FROM ' + ISNULL (QUOTENAME (PARSENAME (@TargetCollectionTable, 2)), '[snapshots]') + '.' + QUOTENAME (PARSENAME (@TargetCollectionTable, 1)) + ' AS dataTable
        INNER JOIN core.snapshots AS s ON dataTable.snapshot_id = s.snapshot_id
        WHERE dataTable.collection_time BETWEEN @start_time_internal AND @end_time_internal
            AND s.snapshot_id BETWEEN @start_snapshot_id AND @end_snapshot_id
            AND s.instance_name = @ServerName AND s.collection_set_uid = @CollectionSetUid'
        
        EXEC sp_executesql 
            @sql, 
            N'@ServerName sysname, @CollectionSetUid nvarchar(64), @start_time_internal datetimeoffset(7), @end_time_internal datetimeoffset(7), @group_interval_min int, 
                @start_snapshot_id int, @end_snapshot_id int', 
            @ServerName = @ServerName, @CollectionSetUid = @CollectionSetUid, @start_time_internal = @start_time_internal, @end_time_internal = @end_time_internal, 
            @group_interval_min = @group_interval_min, @start_snapshot_id = @start_snapshot_id, @end_snapshot_id = @end_snapshot_id
        
        
        -- If the caller doesn't care about anything but the interval boundaries, don't bother returning the collection_time/snapshot_id values
        -- that fall in the middle of an interval. 
        IF (@include_snapshot_detail = 0)
        BEGIN
            SELECT interval_time_id, interval_start_time, interval_end_time, interval_id, 
                MIN (collection_time) AS first_collection_time, MAX (collection_time) AS last_collection_time, 
                MIN (snapshot_id) AS first_snapshot_id, MAX (snapshot_id) AS last_snapshot_id, 
                NULL AS source_id, NULL AS snapshot_id, NULL AS collection_time, NULL AS collection_time_id
            FROM #snapshots
            GROUP BY interval_time_id, interval_start_time, interval_end_time, interval_id
            ORDER BY interval_time_id
        END
        ELSE
        BEGIN
            SELECT interval_info.*, #snapshots.source_id, #snapshots.snapshot_id, #snapshots.collection_time, 
                DENSE_RANK() OVER (ORDER BY #snapshots.collection_time) AS collection_time_id
            FROM 
            (
                SELECT interval_time_id, interval_start_time, interval_end_time, interval_id, 
                    MIN (collection_time) AS first_collection_time, MAX (collection_time) AS last_collection_time, 
                    MIN (snapshot_id) AS first_snapshot_id, MAX (snapshot_id) AS last_snapshot_id
                FROM #snapshots
                GROUP BY interval_time_id, interval_start_time, interval_end_time, interval_id
            ) AS interval_info
            INNER JOIN #snapshots ON interval_info.interval_time_id = #snapshots.interval_time_id
            ORDER BY interval_info.interval_time_id, #snapshots.collection_time
        END
    END
    ELSE BEGIN
        /* Invalid parameter %s specified for %s. */
        RAISERROR (21055, 16, -1, @TargetCollectionTable, '@TargetCollectionTable')
    END
    
END;
GO


--
-- snapshots.rpt_next_and_previous_collection_times
--  Helper proc used by other procs that need to return data from N evenly-spaced intervals within a larger user-specified time window
--
--
-- Parameters: 
--    @ServerName - SQL Server instance name
--    @CollectionTime - End of the user-selected time window (UTC)
--    @TargetCollectionTable - Name of the table from which to harvest the collection_time values. Must have a datetimeoffset [collection_time] 
--        column and be in the [snapshots] or [custom_snapshots] schema. 
--
IF (NOT OBJECT_ID(N'snapshots.rpt_next_and_previous_collection_times', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[rpt_next_and_previous_collection_times] ...'
    DROP PROCEDURE [snapshots].[rpt_next_and_previous_collection_times]
END
GO 

PRINT 'Creating procedure [snapshots].[rpt_next_and_previous_collection_times] ...'
GO
CREATE PROC [snapshots].[rpt_next_and_previous_collection_times]
    @ServerName sysname, 
    @CollectionTime datetime, 
    @DataGroupID nvarchar(128)
AS
BEGIN
    DECLARE @current_collection_time datetimeoffset(7)  -- current collection time
    DECLARE @current_snapshot_id int                    -- current collection time''s snapshot ID
    DECLARE @previous_collection_time datetimeoffset(7) -- next collection time
    DECLARE @next_collection_time datetimeoffset(7)     -- prior collection time
    DECLARE @snapshot_table sysname                     -- name of the snapshot table we'll be querying
    
    -- The assumed time zone offset for this conversion is +00:00 (datetime must be passed in as UTC)
    SET @current_collection_time = CAST (@CollectionTime AS datetimeoffset(7));
    -- Compensate for RS truncation of fractional seconds 
    SET @current_collection_time = DATEADD(second, 1, @CollectionTime);
    
    -- Currently, we only call this stored procedure from one place, and that code only needs next and prev collection times for 
    -- the snapshots.active_sessions_and_requests table. If, in the future, we need to call this for other tables, the three 
    -- SELECT TOP 1 queries below will need to be converted to dynamic SQL, executed via sp_executesql with OUTPUT parameters.  
    -- The correct target table name (@snapshot_table) should be determined based on the @DataGroupID parameter.  
    IF (@DataGroupID = 'SqlActiveRequests')
    BEGIN
        SET @snapshot_table = 'snapshots.active_sessions_and_requests';
    END
    ELSE BEGIN
        /* Invalid parameter %s specified for %s. */
        RAISERROR (21055, 16, -1, @DataGroupID, '@DataGroupID');
        RETURN;
    END
    
    DECLARE @sql nvarchar(max);

    -- Find our exact collection time using the approx time passed in   
    SELECT TOP 1 @current_collection_time = r.collection_time, @current_snapshot_id = r.snapshot_id
    FROM core.snapshots AS s
    INNER JOIN snapshots.active_sessions_and_requests AS r ON s.snapshot_id = r.snapshot_id
    WHERE s.instance_name = @ServerName
        AND r.collection_time <= @current_collection_time
    ORDER BY collection_time DESC;
    
    -- Find the previous collection time
    SELECT TOP 1 @previous_collection_time = r.collection_time
    FROM core.snapshots AS s
    INNER JOIN snapshots.active_sessions_and_requests AS r ON s.snapshot_id = r.snapshot_id
    WHERE s.instance_name = @ServerName
      AND r.collection_time < @current_collection_time 
    ORDER BY collection_time DESC;
    
    -- Find the next collection time
    SELECT TOP 1 @next_collection_time = r.collection_time
    FROM core.snapshots AS s
    INNER JOIN snapshots.active_sessions_and_requests AS r ON s.snapshot_id = r.snapshot_id
    WHERE s.instance_name = @ServerName
      AND r.collection_time > @current_collection_time 
    ORDER BY collection_time ASC;

    IF @previous_collection_time IS NULL SET @previous_collection_time = @current_collection_time;
    IF @next_collection_time IS NULL SET @next_collection_time = @current_collection_time;

    SELECT 
        CONVERT (datetime, SWITCHOFFSET (@current_collection_time, '+00:00')) AS current_collection_time, 
        @current_snapshot_id AS current_snapshot_id, 
        CONVERT (datetime, SWITCHOFFSET (@previous_collection_time, '+00:00')) AS previous_collection_time, 
        CONVERT (datetime, SWITCHOFFSET (@next_collection_time, '+00:00')) AS next_collection_time;
END
GO


--
-- snapshots.rpt_generic_perfmon
--  Returns perf counter data for the counters associated with a "data group id" (typically, a report name). 
-- Parameters: 
--    @ServerName - SQL Server instance name
--    @EndTime - End of selected time window end (UTC)
--    @WindowSize - Number of minutes in the time window
--    @DataGroupID - Name of the report, used to return only the necessary counters
--    @CollectionSetUid - GUID identifier for the target collection set
--    @interval_count - Number of time intervals to divide the time window into (default 40)
--
IF (NOT OBJECT_ID(N'snapshots.rpt_generic_perfmon', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[rpt_generic_perfmon] ...'
    DROP PROCEDURE [snapshots].[rpt_generic_perfmon]
END
GO 

PRINT 'Creating procedure [snapshots].[rpt_generic_perfmon] ...'
GO
CREATE PROCEDURE [snapshots].[rpt_generic_perfmon]
    @ServerName sysname, 
    @EndTime datetime, 
    @WindowSize int, 
    @DataGroupID nvarchar(128), 
    @CollectionSetUid nvarchar(64), 
    @interval_count int = 40
AS
BEGIN
    SET NOCOUNT ON;
    
    DECLARE @start_time_internal datetimeoffset(7);
    DECLARE @end_time_internal datetimeoffset(7);
    
    -- Start time should be passed in as a UTC datetime
    IF (@EndTime IS NOT NULL)
    BEGIN
        -- Assumed time zone offset for this conversion is +00:00 (datetime must be passed in as UTC)
        SET @end_time_internal = CAST (@EndTime AS datetimeoffset(7));
    END
    ELSE BEGIN
        SELECT @end_time_internal = MAX(snapshot_time)
        FROM core.snapshots 
        WHERE instance_name = @ServerName AND collection_set_uid = @CollectionSetUid
    END
    SET @start_time_internal = DATEADD (minute, -1 * @WindowSize, @end_time_internal);
    
    -- Divide the time window up into N equal intervals.  Each interval will correspond to one 
    -- point on a line chart.  Calc the duration of one interval, in seconds. 
    DECLARE @group_interval_sec int
    IF @interval_count < 1 SET @interval_count = 1
    SET @group_interval_sec = ROUND (DATEDIFF (second, @start_time_internal, @end_time_internal) / @interval_count, 0)
    IF @group_interval_sec < 10 SET @group_interval_sec = 10

    -- For counter groups that include the "Process(abc)\% Processor Time" counter (e.g. 'ServerActivity' and 'SystemCpuUsagePivot'), 
    -- we must determine the logical CPU count on the target system by querying the number of "Processor" counter instances we 
    -- captured in a perfmon sample that was captured around the same time. 
    DECLARE @cpu_count smallint
    SET @cpu_count = 1
    IF EXISTS (
        SELECT * FROM [core].[performance_counter_report_group_items] 
        WHERE counter_group_id = @DataGroupID AND [divide_by_cpu_count] = 1
    )
    BEGIN
        SELECT @cpu_count = COUNT (DISTINCT pc.performance_instance_name)
        FROM snapshots.performance_counters AS pc
        INNER JOIN core.snapshots s ON s.snapshot_id = pc.snapshot_id 
        WHERE pc.performance_object_name = 'Processor' AND pc.performance_counter_name = '% Processor Time'
            AND pc.performance_instance_name != '_Total' AND ISNUMERIC (pc.performance_instance_name) = 1
            AND s.instance_name = @ServerName AND s.collection_set_uid = @CollectionSetUid
            AND s.snapshot_id = 
                (SELECT TOP 1 s2.snapshot_id FROM core.snapshots AS s2 
                INNER JOIN snapshots.performance_counters AS pc2 ON s2.snapshot_id = pc2.snapshot_id 
                WHERE s2.snapshot_time > @start_time_internal
                    AND s2.instance_name = @ServerName AND s2.collection_set_uid = @CollectionSetUid
                    AND pc2.performance_object_name = 'Processor' AND pc2.performance_counter_name = '% Processor Time')
            AND pc.collection_time = 
                (SELECT TOP 1 collection_time FROM snapshots.performance_counter_values pcv2 WHERE pcv2.snapshot_id = s.snapshot_id)
        -- These trace flags are necessary for a good plan, due to the join on ascending PK w/range filter
        OPTION (QUERYTRACEON 2389, QUERYTRACEON 2390)
        
        IF ISNULL (@cpu_count, 0) = 0
        BEGIN
            -- This message will never be shown on a report. It is included here only as a troubleshooting aid. 
            RAISERROR ('Unable to determine CPU count. Assuming 1 CPU for process CPU calculations', 9, 1)
            SET @cpu_count = 1
        END
    END

    -- Get the matching performance counter instances for this data group
    SELECT 
        pci.*, 
        cl.counter_group_item_id, cl.counter_group_id, cl.counter_subgroup_id, cl.series_name, 
        cl.multiply_by, cl.divide_by_cpu_count
    INTO #pci
    FROM snapshots.performance_counter_instances AS pci
    INNER JOIN [core].[performance_counter_report_group_items] AS cl 
        ON cl.counter_group_id = @DataGroupID
        AND pci.counter_name = cl.counter_name 
        AND ISNULL(pci.instance_name, N'') LIKE cl.instance_name 
        AND 
        (
            (cl.object_name_wildcards = 0 AND pci.[object_name] = cl.[object_name])
            OR (cl.object_name_wildcards = 1 AND pci.[object_name] LIKE cl.[object_name])
        )
        AND (cl.not_instance_name IS NULL OR pci.instance_name NOT LIKE cl.not_instance_name);
    
    -- Get the perfmon counter values for these counters in each time interval. 
    -- NOTE: If you change the schema of this resultset, you must also update the CREATE TABLE in [rpt_generic_perfmon_pivot]. 
    SELECT 
        #pci.counter_subgroup_id, 
        REPLACE (#pci.series_name, '[COUNTER_INSTANCE]', ISNULL(#pci.instance_name, N'')) AS series_name, 
        -- Using our time window end time (@end_time_internal) as a reference point, divide 
        -- the time window into [@interval_count] intervals of [@group_interval_sec] duration 
        -- per interval. 
        DATEDIFF (second, @end_time_internal, SWITCHOFFSET (CONVERT (datetimeoffset(7), pc.collection_time), '+00:00')) / @group_interval_sec AS interval_id, 
        -- Find the end time for the current time interval, and return as a UTC datetime
        -- Do this by converting [collection_time] into a second count, dividing and multiplying 
        -- the count by [@group_interval_sec] to discard any fraction of an interval, then 
        -- converting the second count back into a datetime.  That datetime is the end point for 
        -- the time interval that this [collection_time] value falls within. 
        CONVERT (datetime, 
            DATEADD (
                second, 
                (DATEDIFF (second, @end_time_internal, SWITCHOFFSET (CONVERT (datetimeoffset(7), pc.collection_time), '+00:00')) / @group_interval_sec) * @group_interval_sec, 
                @end_time_internal
            ) 
        ) AS interval_end_time, 
        #pci.counter_name, 
        AVG( pc.formatted_value * #pci.multiply_by / CASE WHEN #pci.divide_by_cpu_count = 1 THEN @cpu_count ELSE 1 END ) AS avg_formatted_value, 
        MAX( pc.formatted_value * #pci.multiply_by / CASE WHEN #pci.divide_by_cpu_count = 1 THEN @cpu_count ELSE 1 END ) AS max_formatted_value, 
        MIN( pc.formatted_value * #pci.multiply_by / CASE WHEN #pci.divide_by_cpu_count = 1 THEN @cpu_count ELSE 1 END ) AS min_formatted_value, 
        -- This column can be used to simulate a "_Total" instance for multi-instance counters that lack _Total -- use a "%" for #counterlist.instance_name
        -- Expression "1.0 * pc.formatted_value * cl.multiply_by / CASE WHEN cl.divide_by_cpu_count = 1 THEN @cpu_count ELSE 1 END" is the counter value. 
        -- Expression "AVG(<counter_value>) * COUNT (DISTINCT pc.performance_instance_name)" returns the simulated "_Total" instance.
        -- Only valid for multi-instance counters that don't already have a "_Total"). 
        CONVERT (bigint, AVG( 1.0 * pc.formatted_value * #pci.multiply_by / CASE WHEN #pci.divide_by_cpu_count = 1 THEN @cpu_count ELSE 1 END)) 
            * COUNT (DISTINCT #pci.instance_name) AS multi_instance_avg_formatted_value
    FROM snapshots.performance_counter_values AS pc
    INNER JOIN #pci ON #pci.performance_counter_id = pc.performance_counter_instance_id
    INNER JOIN core.snapshots s ON s.snapshot_id = pc.snapshot_id
    WHERE s.instance_name = @ServerName AND s.collection_set_uid = @CollectionSetUid
        AND pc.collection_time BETWEEN @start_time_internal AND @end_time_internal
    GROUP BY 
        DATEDIFF (second, @end_time_internal, SWITCHOFFSET (CONVERT (datetimeoffset(7), pc.collection_time), '+00:00')) / @group_interval_sec, #pci.counter_subgroup_id, 
        #pci.counter_name, 
        REPLACE (#pci.series_name, '[COUNTER_INSTANCE]', ISNULL(#pci.instance_name, N''))
    ORDER BY #pci.counter_subgroup_id, interval_end_time, 2, #pci.counter_name
    -- These trace flags are necessary for a good plan, due to the join on ascending PK w/range filter
    OPTION (QUERYTRACEON 2389, QUERYTRACEON 2390)
END
GO


--
-- snapshots.rpt_generic_perfmon_pivot
--  Pivots the output of [rpt_generic_perfmon] so that multiple counter values can be returned in a 
--  single row. The value of all counters with the same [series_name] will be returned as a single row. 
--  The average of all values in the time window is returned
-- Parameters: 
--    @ServerName - SQL Server instance name
--    @EndTime - End of selected time window end (UTC)
--    @WindowSize - Number of minutes in the time window
--    @DataGroupID - Name of the calling report (used to retrieve the correct counters)
--    @CollectionSetUid - GUID identifier for the target collection set
--    @interval_count - Number of time intervals to divide the time window into (default 40)
--
IF (NOT OBJECT_ID(N'snapshots.rpt_generic_perfmon_pivot', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[rpt_generic_perfmon_pivot] ...'
    DROP PROCEDURE [snapshots].[rpt_generic_perfmon_pivot]
END
GO 

PRINT 'Creating procedure [snapshots].[rpt_generic_perfmon_pivot] ...'
GO
CREATE PROCEDURE [snapshots].[rpt_generic_perfmon_pivot]
    @ServerName sysname, 
    @EndTime datetime, 
    @WindowSize int, 
    @DataGroupID nvarchar(128), 
    @CollectionSetUid varchar(64), 
    @interval_count int = 1
AS
BEGIN
    SET NOCOUNT ON;

    CREATE TABLE #countervalues (
        counter_subgroup_id nvarchar(512),          -- name of the data subgroup (e.g. chart or table) that requested the counter (used by a chart to filter out its rows from the larger resultset)
        series_name nvarchar(1024),                 -- chart series label 
        interval_id int,                            -- not used here
        interval_end_time datetime,                 -- not used here
        performance_counter_name nvarchar(2048),    -- perfmon counter name
        avg_formatted_value bigint,                 -- avg perfmon counter value over the time period
        max_formatted_value bigint,                 -- max perfmon counter value over the time period
        min_formatted_value bigint,                 -- min perfmon counter value over the time period
        multi_instance_avg_formatted_value bigint   -- simulated "_Total" instance value for multi-instance counters that lack _Total
    )
    
    SET @DataGroupID = @DataGroupID + 'Pivot'
    INSERT INTO #countervalues
    EXEC [snapshots].[rpt_generic_perfmon] 
        @ServerName, 
        @EndTime, 
        @WindowSize, 
        @DataGroupID, 
        @CollectionSetUid, 
        @interval_count

    IF EXISTS (SELECT * FROM #countervalues)
    BEGIN
        -- @counterlist looks like "[Counter 1], [Counter 2]"
        DECLARE @counterlist nvarchar(max)
        -- @columnlist_min_inner looks like "[Counter 1] AS [Counter 1_min], [Counter 2] AS [Counter 2_min]"
        DECLARE @columnlist_min_inner nvarchar(max)
        -- @columnlist_max_inner looks like "[Counter 1] AS [Counter 1_max], [Counter 2] AS [Counter 2_max]"
        DECLARE @columnlist_max_inner nvarchar(max)
        -- @columnlist_min_outer looks like "[Counter 1_min], [Counter 2_min]"
        DECLARE @columnlist_min_outer nvarchar(max)
        -- @columnlist_max_outer looks like "[Counter 1_max], [Counter 2_max]"
        DECLARE @columnlist_max_outer nvarchar(max)
        SET @counterlist = ''
        SET @columnlist_min_inner = ''
        SET @columnlist_min_outer = ''
        SET @columnlist_max_inner = ''
        SET @columnlist_max_outer = ''
        
        -- Build counter lists
        SELECT 
            @counterlist = @counterlist 
                -- Escape any embedded ']' chars (we can't use QUOTENAME because it can't handle strings > 128 chars)
                + ', [' + REPLACE (performance_counter_name, ']', ']]') + ']'
            , @columnlist_min_outer = @columnlist_min_outer + ', [' + REPLACE (performance_counter_name, ']', ']]') + '_min]'
            , @columnlist_min_inner = @columnlist_min_inner + ', [' + REPLACE (performance_counter_name, ']', ']]') + ']'
                + ' AS [' + REPLACE (performance_counter_name, ']', ']]') + '_min]'
            , @columnlist_max_outer = @columnlist_max_outer + ', [' + REPLACE (performance_counter_name, ']', ']]') + '_max]'
            , @columnlist_max_inner = @columnlist_max_inner + ', [' + REPLACE (performance_counter_name, ']', ']]') + ']'
                + ' AS [' + REPLACE (performance_counter_name, ']', ']]') + '_max]'
        FROM (SELECT DISTINCT performance_counter_name FROM #countervalues) AS t
        GROUP BY performance_counter_name 
        OPTION (MAXDOP 1)
        -- Remove the leading comma
        SET @counterlist = SUBSTRING (@counterlist, 3, LEN (@counterlist)-2)
        SET @columnlist_min_inner = SUBSTRING (@columnlist_min_inner, 3, LEN (@columnlist_min_inner)-2)
        SET @columnlist_min_outer = SUBSTRING (@columnlist_min_outer, 3, LEN (@columnlist_min_outer)-2)
        SET @columnlist_max_inner = SUBSTRING (@columnlist_max_inner, 3, LEN (@columnlist_max_inner)-2)
        SET @columnlist_max_outer = SUBSTRING (@columnlist_max_outer, 3, LEN (@columnlist_max_outer)-2)

        -- We have to use three PIVOTs here because SQL only allows one aggregate function 
        -- per PIVOT, and we need AVG, MIN, and MAX.  They are over a very small temp table, 
        -- though (by default just one row per counter), so the execution cost isn't 
        -- excessive. 
        DECLARE @sql nvarchar(max)
        SET @sql = '
            SELECT avg_pivot.*, ' + @columnlist_min_outer + ', ' + @columnlist_max_outer + '
            FROM 
            (
                SELECT series_name, interval_end_time, ' + @counterlist + '
                FROM 
                (
                    SELECT series_name, interval_end_time, performance_counter_name, avg_formatted_value
                    FROM #countervalues
                ) AS SourceTable
                PIVOT 
                (
                    AVG (avg_formatted_value) 
                    FOR performance_counter_name IN (' + @counterlist + ')
                ) AS PivotTable
            ) AS avg_pivot
            
            INNER JOIN 
            (
                SELECT series_name, interval_end_time, ' + @columnlist_min_inner + '
                FROM 
                (
                    SELECT series_name, interval_end_time, performance_counter_name, min_formatted_value
                    FROM #countervalues
                ) AS SourceTable
                PIVOT 
                (
                    MIN (min_formatted_value) 
                    FOR performance_counter_name IN (' + @counterlist + ')
                ) AS PivotTable
            ) AS min_pivot ON min_pivot.series_name = avg_pivot.series_name AND min_pivot.interval_end_time = avg_pivot.interval_end_time
            
            INNER JOIN 
            (
                SELECT series_name, interval_end_time, ' + @columnlist_max_inner + '
                FROM 
                (
                    SELECT series_name, interval_end_time, performance_counter_name, max_formatted_value
                    FROM #countervalues
                ) AS SourceTable
                PIVOT 
                (
                    MAX (max_formatted_value) 
                    FOR performance_counter_name IN (' + @counterlist + ')
                ) AS PivotTable
            ) AS max_pivot ON max_pivot.series_name = avg_pivot.series_name AND max_pivot.interval_end_time = avg_pivot.interval_end_time
            '
        EXEC sp_executesql @sql
    END
END;
GO


--
-- snapshots.rpt_wait_stats
--  Returns wait time per wait type over a time interval
-- Parameters: 
--    @ServerName - SQL Server instance name
--    @EndTime - End of the user-selected time window (UTC)
--    @WindowSize - Number of minutes in the time window 
--    @CategoryName - (Optional) Name of wait category to filter on (all categories if NULL)
--    @WaitType - (Optional) Name of wait type to filter on (all wait types if NULL)
--
IF (NOT OBJECT_ID(N'snapshots.rpt_wait_stats', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[rpt_wait_stats] ...'
    DROP PROCEDURE [snapshots].[rpt_wait_stats]
END
GO 

PRINT 'Creating procedure [snapshots].[rpt_wait_stats] ...'
GO
CREATE PROCEDURE [snapshots].[rpt_wait_stats]
    @ServerName sysname,
    @EndTime datetime = NULL,
    @WindowSize int,
    @CategoryName nvarchar(20) = NULL, 
    @WaitType nvarchar(45) = NULL
AS
BEGIN
    SET NOCOUNT ON;

    -- Clean string params (on drillthrough, RS may pass in empty string instead of NULL)
    IF @CategoryName = '' SET @CategoryName = NULL
    IF @WaitType = '' SET @WaitType = NULL

    -- Divide our time window up into 40 evenly-sized time intervals, and find the last collection_time within each of these intervals
    CREATE TABLE #intervals (
        interval_time_id        int, 
        interval_start_time     datetimeoffset(7),
        interval_end_time       datetimeoffset(7),
        interval_id             int, 
        first_collection_time   datetimeoffset(7), 
        last_collection_time    datetimeoffset(7), 
        first_snapshot_id       int,
        last_snapshot_id        int, 
        source_id               int,
        snapshot_id             int, 
        collection_time         datetimeoffset(7), 
        collection_time_id      int
    )
    -- GUID 49268954-... is Server Activity
    INSERT INTO #intervals
    EXEC [snapshots].[rpt_interval_collection_times] 
        @ServerName, @EndTime, @WindowSize, 'snapshots.os_wait_stats', '49268954-4FD4-4EB6-AA04-CD59D9BB5714', 40, 0

    -- Get the earliest and latest snapshot_id values that contain data for the selected time interval. 
    -- This will allow a more efficient query plan. 
    DECLARE @start_snapshot_id int;
    DECLARE @end_snapshot_id int;
    SELECT @start_snapshot_id = MIN (first_snapshot_id)
    FROM #intervals
    SELECT @end_snapshot_id = MAX (last_snapshot_id)
    FROM #intervals
    
    -- Get the wait stats for these collection times
	SELECT 
		coll.interval_time_id, coll.interval_id, 
		last_collection_time AS collection_time, 
		coll.interval_start_time, 
		coll.interval_end_time, 
		coll.last_snapshot_id, 
		wt.category_name, ws.wait_type, ws.waiting_tasks_count, 
        ISNULL (ws.signal_wait_time_ms, 0) AS signal_wait_time_ms, 
        ISNULL (ws.wait_time_ms, 0) - ISNULL (ws.signal_wait_time_ms, 0) AS wait_time_ms, 
        wait_time_ms AS raw_wait_time_ms, 
        ISNULL (ws.wait_time_ms, 0) AS wait_time_ms_cumulative 
    INTO #wait_stats
    FROM snapshots.os_wait_stats AS ws
    INNER JOIN #intervals AS coll ON coll.last_snapshot_id = ws.snapshot_id AND coll.last_collection_time = ws.collection_time 
    INNER JOIN core.wait_types_categorized AS wt ON wt.wait_type = ws.wait_type
    WHERE wt.category_name = ISNULL (@CategoryName, wt.category_name)
        AND wt.wait_type = ISNULL (@WaitType, wt.wait_type)
        AND wt.ignore != 1

    -- Get wait times by waittype for each interval (plus CPU time, modeled as a waittype)
    ---- First get resource wait stats for this interval. We must convert all datetimeoffset values 
    ---- to UTC datetime values before returning to Reporting Services
    SELECT 
        CONVERT (datetime, SWITCHOFFSET (CAST (w1.collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        CONVERT (datetime, SWITCHOFFSET (CAST (w1.interval_start_time AS datetimeoffset(7)), '+00:00')) AS interval_start, 
        CONVERT (datetime, SWITCHOFFSET (CAST (w2.interval_start_time AS datetimeoffset(7)), '+00:00')) AS interval_end, 
        w2.category_name, w2.wait_type, 
        -- All wait stats will be reset to zero by a service cycle, which will cause 
        -- (snapshot2waittime-snapshot1waittime) calculations to produce an incorrect 
        -- negative wait time for the interval.  Detect this and avoid calculating 
        -- negative wait time/wait count/signal time deltas. 
        CASE 
            WHEN (w2.waiting_tasks_count - w1.waiting_tasks_count) < 0 THEN w2.waiting_tasks_count 
            ELSE (w2.waiting_tasks_count - w1.waiting_tasks_count) 
        END AS waiting_tasks_count_delta, 
        CASE 
            WHEN (w2.raw_wait_time_ms - w1.raw_wait_time_ms) < 0 THEN w2.wait_time_ms
            ELSE (w2.wait_time_ms - w1.wait_time_ms)
        END AS resource_wait_time_delta, -- / CAST (DATEDIFF (second, w1.collection_time, w2.collection_time) AS decimal) 
        CASE 
            WHEN (w2.signal_wait_time_ms - w1.signal_wait_time_ms) < 0 THEN w2.signal_wait_time_ms 
            ELSE (w2.signal_wait_time_ms - w1.signal_wait_time_ms) 
        END AS resource_signal_time_delta, -- / CAST (DATEDIFF (second, w1.collection_time, w2.collection_time) AS decimal) 
        DATEDIFF (second, w1.collection_time, w2.collection_time) AS interval_sec, 
        w2.wait_time_ms_cumulative
    -- Self-join - w1 represents wait stats at the beginning of the sample interval, while w2 
    -- shows the wait stats at the end of the interval. 
    FROM #wait_stats AS w1 
    INNER JOIN #wait_stats AS w2 ON w1.wait_type = w2.wait_type AND w1.interval_id = w2.interval_id-1 

    UNION ALL 

    ---- Treat the sum of all signal waits as CPU "wait time"
    SELECT 
        MAX (CONVERT (datetime, SWITCHOFFSET (CAST (w1.collection_time AS datetimeoffset(7)), '+00:00'))) AS collection_time, 
        MIN (CONVERT (datetime, SWITCHOFFSET (CAST (w1.interval_start_time AS datetimeoffset(7)), '+00:00'))) AS interval_start, 
        MAX (CONVERT (datetime, SWITCHOFFSET (CAST (w2.interval_start_time AS datetimeoffset(7)), '+00:00'))) AS interval_end, 
        'CPU' AS category_name, 
        'CPU (Signal Wait)' AS wait_type, 
        0 AS waiting_tasks_count_delta, 
        -- Handle wait stats resets, as in the previous query
        SUM (
            CASE 
                WHEN (w2.signal_wait_time_ms - w1.signal_wait_time_ms) < 0 THEN w2.signal_wait_time_ms
                ELSE (w2.signal_wait_time_ms - w1.signal_wait_time_ms) 
            END -- / CAST (DATEDIFF (second, w1.collection_time, w2.collection_time) AS decimal)
        ) AS resource_wait_time_delta, 
        0 AS resource_signal_time_delta, 
        DATEDIFF (second, w1.collection_time, w2.collection_time) AS interval_sec, 
        NULL AS wait_time_ms_cumulative
    FROM #wait_stats AS w1
    INNER JOIN #wait_stats AS w2 ON w1.wait_type = w2.wait_type AND w1.interval_id = w2.interval_id-1
    -- Only return CPU stats if we were told to return the 'CPU' category or all categories
    WHERE (@CategoryName IS NULL OR @CategoryName = 'CPU')
    GROUP BY 
        w1.interval_start_time, w2.interval_start_time, w1.interval_end_time, w2.interval_end_time, w1.collection_time, w2.collection_time

    UNION ALL 

    -- Get actual used CPU time from perfmon data (average across the whole snapshot_time_id interval, 
    -- and use this average for each sample time in this interval).  Note that the "% Processor Time" 
    -- counter in the "Process" object can exceed 100% (for example, it can range from 0-800% on an 
    -- 8 CPU server). 
    SELECT
        CONVERT (datetime, SWITCHOFFSET (CAST (w1.collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        CONVERT (datetime, SWITCHOFFSET (CAST (w1.interval_start_time AS datetimeoffset(7)), '+00:00')) AS interval_start, 
        CONVERT (datetime, SWITCHOFFSET (CAST (w2.interval_start_time AS datetimeoffset(7)), '+00:00')) AS interval_end, 
        'CPU' AS category_name, 
        'CPU (Consumed)' AS wait_type, 
        0 AS waiting_tasks_count_delta, 
        -- Get sqlservr %CPU usage for the perfmon sample that immediately precedes each wait stats sample.  
        -- Multiply by 10 to convert "% CPU" to "ms CPU/sec". This works because (for example) on an 8 proc 
        -- server, Process(...)\% Processor Time ranges from 0 to 800, not 0 to 100.  Multiply again by 
        -- the duration of interval in seconds to get the total ms of CPU time used in the interval. 
        DATEDIFF (second, w1.collection_time, w2.collection_time) * 10 * ( 
            SELECT TOP 1 formatted_value
            FROM snapshots.performance_counters AS pc
            INNER JOIN core.snapshots s ON pc.snapshot_id = s.snapshot_id
            WHERE pc.performance_object_name = 'Process' AND pc.performance_counter_name = '% Processor Time' 
                AND pc.performance_instance_name = '$(TARGETPROCESS)'
                AND pc.collection_time <= w2.collection_time
                AND s.instance_name = @ServerName AND s.collection_set_uid = '49268954-4FD4-4EB6-AA04-CD59D9BB5714' -- Server Activity CS
                AND s.snapshot_id BETWEEN @start_snapshot_id AND @end_snapshot_id 
            ORDER BY pc.collection_time DESC
        ) AS resource_wait_time_delta, 
        0 AS resource_signal_time_delta, 
        DATEDIFF (second, w1.collection_time, w2.collection_time) AS interval_sec, 
        NULL AS wait_time_ms_cumulative
    FROM #wait_stats AS w1
    INNER JOIN #wait_stats AS w2 ON w1.wait_type = w2.wait_type AND w1.interval_id = w2.interval_id-1
    -- Only return CPU stats if we weren't passed in a specific wait category
    WHERE (@CategoryName IS NULL OR @CategoryName = 'CPU')
    GROUP BY 
        w1.interval_start_time, w2.interval_start_time, w1.interval_end_time, w2.interval_end_time, w1.collection_time, w2.collection_time

    ORDER BY category_name, collection_time, wait_type
    -- These trace flags are necessary for a good plan, due to the join on ascending core.snapshot PK
    OPTION (QUERYTRACEON 2389, QUERYTRACEON 2390);
    
END
GO


--
-- snapshots.fn_hexstrtovarbin
--  Converts a hex string into a varbinary(max). Reporting Services does not support binary 
--  form parameters, so sql_handle and plan_handle values must be passed to and from RS as strings. 
--  For conversion in the opposite direction, use master.dbo.fn_varbintohexstr. 
-- Parameters: 
--    @hexStr - a string representation of a binary value (e.g. "0x123C2F")
-- Returns: the input value converted to a true varbinary
--
IF (NOT OBJECT_ID (N'[snapshots].[fn_hexstrtovarbin]', 'FN') IS NULL)
BEGIN
    PRINT 'Dropping function [snapshots].[fn_hexstrtovarbin] ...'
    DROP FUNCTION [snapshots].[fn_hexstrtovarbin]
END
GO 

PRINT 'Creating function [snapshots].[fn_hexstrtovarbin] ...'
GO
CREATE FUNCTION [snapshots].[fn_hexstrtovarbin]
(
    @hexStr varchar(max) 
)
RETURNS varbinary(max)
AS
BEGIN
    DECLARE @ret varbinary(max)
    DECLARE @len int

    SET @ret = 0x;
    SET @len = LEN (@hexStr)-2;

    IF (@len >= 0) AND (LEFT (@hexStr, 2) = '0x')
        SET @hexStr = SUBSTRING (@hexStr, 3, @len);
    ELSE
        RETURN NULL;

    DECLARE @leftNibbleChar char(1), @rightNibbleChar char(1), @hexCharStr varchar(2)
    DECLARE @leftNibble int, @rightNibble int
    DECLARE @i int;
    SET @i = 1;
    WHILE (@i <= @len)
    BEGIN
        SET @hexCharStr = SUBSTRING (@hexStr, @i, 2)
        IF LEN (@hexCharStr) = 1 SET @hexCharStr = '0' + @hexCharStr
        SET @leftNibbleChar = LOWER (LEFT (@hexCharStr, 1))
        SET @rightNibbleChar = LOWER (RIGHT (@hexCharStr, 1))

        IF @leftNibbleChar BETWEEN 'a' AND 'f' COLLATE Latin1_General_BIN 
           SET @leftNibble = (CONVERT (int, CONVERT (binary(1), @leftNibbleChar)) - CONVERT (int, CONVERT (binary(1), 'a')) + 10) * 16;
        ELSE IF @leftNibbleChar BETWEEN '0' AND '9' COLLATE Latin1_General_BIN 
           SET @leftNibble = (CONVERT (int, CONVERT (binary(1), @leftNibbleChar)) - CONVERT (int, CONVERT (binary(1), '0'))) * 16;
        ELSE
            RETURN NULL;

        IF @rightNibbleChar BETWEEN 'a' AND 'f' COLLATE Latin1_General_BIN 
           SET @rightNibble = (CONVERT (int, CONVERT (binary(1), @rightNibbleChar)) - CONVERT (int, CONVERT (binary(1), 'a')) + 10);
        ELSE IF @rightNibbleChar  BETWEEN '0' AND '9' COLLATE Latin1_General_BIN 
           SET @rightNibble = (CONVERT (int, CONVERT (binary(1), @rightNibbleChar)) - CONVERT (int, CONVERT (binary(1), '0')));
        ELSE
            RETURN NULL;

        SET @ret = @ret + CONVERT (binary(1), @leftNibble + @rightNibble)
        SET @i = @i + 2
    END

    RETURN @ret
END
GO


--
-- snapshots.rpt_top_query_stats
--  Returns aggregate query stats for the most expensive notable queries observed 
--  over the specified time interval.
--  Returns the top 10 most expensive plans (ranking criteria is specified via @order_by_criteria). 
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @start_time - (Optional) time window start (UTC)
--    @end_time - time window end (UTC)
--    @time_window_size - Number of intervals in the time window (provide if @start_time is NULL)
--    @time_interval_min - Number of minutes in each interval (provide if @start_time is NULL)
--    @order_by_criteria - (Optional) 'CPU' (default), 'Physical Reads', 'Logical Writes', 'I/O' (reads + writes), or 'Duration'
--    @database_name - Optional filter criteria: Only queries within a particular database
--
IF (NOT OBJECT_ID(N'snapshots.rpt_top_query_stats', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[rpt_top_query_stats] ...'
    DROP PROCEDURE [snapshots].[rpt_top_query_stats]
END
GO 

PRINT 'Creating procedure [snapshots].[rpt_top_query_stats] ...'
GO
CREATE PROCEDURE [snapshots].[rpt_top_query_stats]
    @instance_name sysname,
    @start_time datetime = NULL,
    @end_time datetime = NULL,
    @time_window_size smallint = NULL,
    @time_interval_min smallint = 1, 
    @order_by_criteria varchar(30) = 'CPU', 
    @database_name nvarchar(255) = NULL
AS
BEGIN
    SET NOCOUNT ON;

    -- Clean string params (on drillthrough, RS may pass in an empty string instead of NULL)
    IF @database_name = '' SET @database_name = NULL

    -- @end_time should never be NULL when we are called from the Query Stats report
    -- Convert snapshot_time (datetimeoffset) to a UTC datetime
    IF (@end_time IS NULL)
        SET @end_time = CONVERT (datetime, SWITCHOFFSET (CAST ((SELECT MAX(snapshot_time) FROM core.snapshots) AS datetimeoffset(7)), '+00:00'));

    IF (@start_time IS NULL)
    BEGIN
        -- If time_window_size and time_interval_min are set use them
        -- to determine the start time
        -- Otherwise use the earliest available snapshot_time
        IF @time_window_size IS NOT NULL AND @time_interval_min IS NOT NULL
        BEGIN
            SET @start_time = DATEADD(minute, @time_window_size * @time_interval_min * -1.0, @end_time);
        END
        ELSE
        BEGIN
            -- Convert min snapshot_time (datetimeoffset) to a UTC datetime
            SET @start_time = CONVERT (datetime, SWITCHOFFSET (CAST ((SELECT MIN(snapshot_time) FROM core.snapshots) AS datetimeoffset(7)), '+00:00'));
        END
    END

    DECLARE @end_snapshot_time_id int;
    SELECT @end_snapshot_time_id = MAX(snapshot_time_id) FROM core.snapshots WHERE snapshot_time <= @end_time;

    DECLARE @start_snapshot_time_id int;
    SELECT @start_snapshot_time_id = MIN(snapshot_time_id) FROM core.snapshots WHERE snapshot_time >= @start_time;

    DECLARE @interval_sec int;
    SET @interval_sec = DATEDIFF (s, @start_time, @end_time)

    SELECT 
        REPLACE (REPLACE (REPLACE (REPLACE (REPLACE (REPLACE (
            LEFT (LTRIM (stmtsql.query_text), 100)
            , CHAR(9), ' '), CHAR(10), ' '), CHAR(13), ' '), '   ', ' '), '  ', ' '), '  ', ' ') AS flat_query_text, 
        t.*, 
        master.dbo.fn_varbintohexstr (t.sql_handle) AS sql_handle_str, 
        stmtsql.*
    FROM 
    (
        SELECT TOP 10 
            stat.sql_handle, stat.statement_start_offset, stat.statement_end_offset, snap.source_id, 
            SUM (stat.snapshot_execution_count) AS execution_count, 
            SUM (stat.snapshot_execution_count) / (@interval_sec / 60) AS executions_per_min, 
            SUM (stat.snapshot_worker_time / 1000) AS total_cpu, 
            SUM (stat.snapshot_worker_time / 1000) / @interval_sec AS avg_cpu_per_sec, 
            SUM (stat.snapshot_worker_time / 1000.0) / CASE SUM (stat.snapshot_execution_count) WHEN 0 THEN 1 ELSE SUM (stat.snapshot_execution_count) END AS avg_cpu_per_exec, 
            SUM (stat.snapshot_physical_reads) AS total_physical_reads, 
            SUM (stat.snapshot_physical_reads) / @interval_sec AS avg_physical_reads_per_sec, 
            SUM (stat.snapshot_physical_reads) / CASE SUM (stat.snapshot_execution_count) WHEN 0 THEN 1 ELSE SUM (stat.snapshot_execution_count) END AS avg_physical_reads_per_exec, 
            SUM (stat.snapshot_logical_writes) AS total_logical_writes, 
            SUM (stat.snapshot_logical_writes) / @interval_sec AS avg_logical_writes_per_sec, 
            SUM (stat.snapshot_logical_writes) / CASE SUM (stat.snapshot_execution_count) WHEN 0 THEN 1 ELSE SUM (stat.snapshot_execution_count) END AS avg_logical_writes_per_exec, 
            SUM (stat.snapshot_elapsed_time / 1000) AS total_elapsed_time, 
            SUM (stat.snapshot_elapsed_time / 1000) / @interval_sec AS avg_elapsed_time_per_sec, 
            SUM (stat.snapshot_elapsed_time / 1000.0) / CASE SUM (stat.snapshot_execution_count) WHEN 0 THEN 1 ELSE SUM (stat.snapshot_execution_count) END AS avg_elapsed_time_per_exec, 
            COUNT(*) AS row_count, COUNT (DISTINCT stat.creation_time) AS plan_count, 
            SUM (CASE @order_by_criteria WHEN 'Duration' THEN charted_value / 1000 ELSE charted_value END) AS charted_value, 
            -- TODO: Make this "sql.database_name" once database name is available in notable_query_text (VSTS #121662)
            CONVERT (nvarchar(255), '') AS database_name, 
            ROW_NUMBER() OVER (ORDER BY SUM (charted_value) DESC) as query_rank
        FROM 
        (
            SELECT *, 
                CASE @order_by_criteria 
                    WHEN 'CPU' THEN (snapshot_worker_time / 1000.0) / @interval_sec
                    WHEN 'Physical Reads' THEN 1.0 * snapshot_physical_reads / @interval_sec
                    WHEN 'Logical Writes' THEN 1.0 * snapshot_logical_writes / @interval_sec
                    WHEN 'I/O' THEN 1.0 * (snapshot_physical_reads + snapshot_logical_writes) / @interval_sec
                    WHEN 'Duration' THEN snapshot_elapsed_time / 1000.0
                    ELSE (snapshot_worker_time / 1000.0) / @interval_sec
                END AS charted_value
            FROM snapshots.query_stats 
        ) AS stat
        INNER JOIN core.snapshots snap ON stat.snapshot_id = snap.snapshot_id
        -- TODO: Uncomment this and the line in the WHERE clause once database name is available in notable_query_text (VSTS #121662)
        -- LEFT OUTER JOIN snapshots.notable_query_text AS sql ON t.sql_handle = sql.sql_handle and sql.source_id = t.source_id
        WHERE
            snap.instance_name = @instance_name 
            AND snap.snapshot_time_id BETWEEN @start_snapshot_time_id AND @end_snapshot_time_id
            --  AND ISNULL (sql.database_name = ISNULL (@database_name, stat.database_name)
        GROUP BY stat.sql_handle, stat.statement_start_offset, stat.statement_end_offset, snap.source_id
        ORDER BY ROW_NUMBER() OVER (ORDER BY SUM (charted_value) DESC) ASC
    ) AS t
    LEFT OUTER JOIN snapshots.notable_query_text sql ON t.sql_handle = sql.sql_handle and sql.source_id = t.source_id
    OUTER APPLY snapshots.fn_get_query_text (t.source_id, t.sql_handle, t.statement_start_offset, t.statement_end_offset) AS stmtsql
    ORDER BY query_rank ASC
    -- These trace flags are necessary for a good plan, due to the join on ascending PK w/range filter
    OPTION (QUERYTRACEON 2389, QUERYTRACEON 2390)
END
GO


--
-- snapshots.rpt_query_stats
--  Returns aggregate query stats for all executions of a particular query within a specified time interval
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @start_time - (Optional) time window start (UTC)
--    @end_time - time window end (UTC)
--    @time_window_size - Number of intervals in the time window (provide if @start_time is NULL)
--    @time_interval_min - Number of minutes in each interval (provide if @start_time is NULL)
--    @sql_handle_str - String representation of a SQL handle (e.g. "0x1F27BC...")
--    @statement_start_offset - Start offset (byte count) for the statement within the batch identified by @sql_handle_str
--    @statement_start_offset - End offset (byte count) for the statement within the batch identified by @sql_handle_str
--
IF (NOT OBJECT_ID(N'snapshots.rpt_query_stats', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[rpt_query_stats] ...'
    DROP PROCEDURE [snapshots].[rpt_query_stats]
END
GO 

PRINT 'Creating procedure [snapshots].[rpt_query_stats] ...'
GO
CREATE PROCEDURE [snapshots].[rpt_query_stats]
    @instance_name sysname,
    @start_time datetime = NULL,
    @end_time datetime = NULL,
    @time_window_size smallint,
    @time_interval_min smallint = 1, 
    @sql_handle_str varchar(130), 
    @statement_start_offset int, 
    @statement_end_offset int
AS
BEGIN
    SET NOCOUNT ON;

    -- @end_time should never be NULL when we are called from the Query Stats report
    -- Convert snapshot_time (datetimeoffset) to a UTC datetime
    IF (@end_time IS NULL)
        SET @end_time = CONVERT (datetime, SWITCHOFFSET (CAST ((SELECT MAX(snapshot_time) FROM core.snapshots) AS datetimeoffset(7)), '+00:00'));

    IF (@start_time IS NULL)
    BEGIN
        -- If time_window_size and time_interval_min are set use them
        -- to determine the start time
        -- Otherwise use the earliest available snapshot_time
        IF @time_window_size IS NOT NULL AND @time_interval_min IS NOT NULL
        BEGIN
            SET @start_time = DATEADD(minute, @time_window_size * @time_interval_min * -1.0, @end_time);
        END
        ELSE
        BEGIN
            -- Convert min snapshot_time (datetimeoffset) to a UTC datetime
            SET @start_time = CONVERT (datetime, SWITCHOFFSET (CAST ((SELECT MIN(snapshot_time) FROM core.snapshots) AS datetimeoffset(7)), '+00:00'));
        END
    END

    DECLARE @end_snapshot_time_id int;
    SELECT @end_snapshot_time_id = MAX(snapshot_time_id) FROM core.snapshots WHERE snapshot_time <= @end_time;

    DECLARE @start_snapshot_time_id int;
    SELECT @start_snapshot_time_id = MIN(snapshot_time_id) FROM core.snapshots WHERE snapshot_time >= @start_time;

    DECLARE @interval_sec int;
    SET @interval_sec = DATEDIFF (s, @start_time, @end_time);

    DECLARE @sql_handle varbinary(64)
    SET @sql_handle = snapshots.fn_hexstrtovarbin (@sql_handle_str)

    SELECT 
        REPLACE (REPLACE (REPLACE (REPLACE (REPLACE (REPLACE (
            LEFT (LTRIM (stmtsql.query_text), 100)
            , CHAR(9), ' '), CHAR(10), ' '), CHAR(13), ' '), '   ', ' '), '  ', ' '), '  ', ' ') AS flat_query_text, 
        t.*, 
        master.dbo.fn_varbintohexstr (t.sql_handle) AS sql_handle_str, 
        stmtsql.*
    FROM 
    (
        SELECT 
            stat.sql_handle, stat.statement_start_offset, stat.statement_end_offset, snap.source_id, 
            SUM (stat.snapshot_execution_count) AS execution_count, 
            SUM (stat.snapshot_execution_count) / (@interval_sec / 60) AS executions_per_min, 
            SUM (stat.snapshot_worker_time / 1000) AS total_cpu, 
            SUM (stat.snapshot_worker_time / 1000) / @interval_sec AS avg_cpu_per_sec, 
            SUM (stat.snapshot_worker_time / 1000.0) / CASE SUM (stat.snapshot_execution_count) WHEN 0 THEN 1 ELSE SUM (stat.snapshot_execution_count) END AS avg_cpu_per_exec, 
            SUM (stat.snapshot_physical_reads) AS total_physical_reads, 
            SUM (stat.snapshot_physical_reads) / @interval_sec AS avg_physical_reads_per_sec, 
            SUM (stat.snapshot_physical_reads) / CASE SUM (stat.snapshot_execution_count) WHEN 0 THEN 1 ELSE SUM (stat.snapshot_execution_count) END AS avg_physical_reads_per_exec, 
            SUM (stat.snapshot_logical_writes) AS total_logical_writes, 
            SUM (stat.snapshot_logical_writes) / @interval_sec AS avg_logical_writes_per_sec, 
            SUM (stat.snapshot_logical_writes) / CASE SUM (stat.snapshot_execution_count) WHEN 0 THEN 1 ELSE SUM (stat.snapshot_execution_count) END AS avg_logical_writes_per_exec, 
            SUM (stat.snapshot_elapsed_time / 1000) AS total_elapsed_time, 
            SUM (stat.snapshot_elapsed_time / 1000) / @interval_sec AS avg_elapsed_time_per_sec, 
            SUM (stat.snapshot_elapsed_time / 1000.0) / CASE SUM (stat.snapshot_execution_count) WHEN 0 THEN 1 ELSE SUM (stat.snapshot_execution_count) END AS avg_elapsed_time_per_exec, 
            COUNT(*) AS row_count, COUNT (DISTINCT stat.creation_time) AS plan_count 
        FROM snapshots.query_stats AS stat
        INNER JOIN core.snapshots snap ON stat.snapshot_id = snap.snapshot_id
        WHERE
            snap.instance_name = @instance_name 
            AND stat.sql_handle = @sql_handle 
            AND stat.statement_start_offset = @statement_start_offset 
            AND stat.statement_end_offset = @statement_end_offset
            AND snap.snapshot_time_id BETWEEN @start_snapshot_time_id AND @end_snapshot_time_id
        GROUP BY stat.sql_handle, stat.statement_start_offset, stat.statement_end_offset, snap.source_id
    ) t
    LEFT OUTER JOIN snapshots.notable_query_text sql ON t.sql_handle = sql.sql_handle and sql.source_id = t.source_id
    OUTER APPLY snapshots.fn_get_query_text (t.source_id, t.sql_handle, t.statement_start_offset, t.statement_end_offset) AS stmtsql
    -- These trace flags are necessary for a good plan, due to the join on ascending PK w/range filter
    OPTION (QUERYTRACEON 2389, QUERYTRACEON 2390)
END
GO


--
-- snapshots.rpt_query_plan_stats
--  Returns aggregate stats for the all plans observed for a query within a specified time interval. 
--  Returns the top 10 most expensive plans (ranking criteria is specified via @order_by_criteria). 
--    @instance_name - SQL Server instance name
--    @start_time - (Optional) time window start (UTC)
--    @end_time - time window end (UTC)
--    @time_window_size - Number of intervals in the time window (provide if @start_time is NULL)
--    @time_interval_min - Number of minutes in each interval (provide if @start_time is NULL)
--    @sql_handle_str - String representation of a SQL handle (e.g. "0x1F27BC...")
--    @plan_handle_str - String representation of a plan handle (e.g. "0x1F27BC...")
--    @plan_creation_time - (Optional) Plan creation time
--    @statement_start_offset - Start offset (byte count) for the statement within the batch identified by @sql_handle_str
--    @statement_start_offset - End offset (byte count) for the statement within the batch identified by @sql_handle_str
--    @order_by_criteria - (Optional) 'CPU' (default), 'Physical Reads', 'Logical Writes', 'I/O' (reads+writes), or 'Duration'
--
IF (NOT OBJECT_ID(N'snapshots.rpt_query_plan_stats', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[rpt_query_plan_stats] ...'
    DROP PROCEDURE [snapshots].[rpt_query_plan_stats]
END
GO 

PRINT 'Creating procedure [snapshots].[rpt_query_plan_stats] ...'
GO
CREATE PROCEDURE [snapshots].[rpt_query_plan_stats]
    @instance_name sysname,
    @start_time datetime = NULL,
    @end_time datetime = NULL,
    @time_window_size smallint,
    @time_interval_min smallint = 1, 
    @sql_handle_str varchar(130), 
    @plan_handle_str varchar(130) = NULL, 
    @plan_creation_time datetime = NULL, 
    @statement_start_offset int, 
    @statement_end_offset int, 
    @order_by_criteria varchar(30) = 'CPU'
AS
BEGIN
    SET NOCOUNT ON;

    -- @end_time should never be NULL when we are called from the Query Stats report
    -- Convert snapshot_time (datetimeoffset) to a UTC datetime
    IF (@end_time IS NULL)
        SET @end_time = CONVERT (datetime, SWITCHOFFSET (CAST ((SELECT MAX(snapshot_time) FROM core.snapshots) AS datetimeoffset(7)), '+00:00'));

    IF (@start_time IS NULL)
    BEGIN
        -- If time_window_size and time_interval_min are set use them
        -- to determine the start time
        -- Otherwise use the earliest available snapshot_time
        IF @time_window_size IS NOT NULL AND @time_interval_min IS NOT NULL
        BEGIN
            SET @start_time = DATEADD(minute, @time_window_size * @time_interval_min * -1.0, @end_time);
        END
        ELSE
        BEGIN
            -- Convert min snapshot_time (datetimeoffset) to a UTC datetime
            SET @start_time = CONVERT (datetime, SWITCHOFFSET (CAST ((SELECT MIN(snapshot_time) FROM core.snapshots) AS datetimeoffset(7)), '+00:00'));
        END
    END

    DECLARE @end_snapshot_time_id int;
    SELECT @end_snapshot_time_id = MAX(snapshot_time_id) FROM core.snapshots WHERE snapshot_time <= @end_time;

    DECLARE @start_snapshot_time_id int;
    SELECT @start_snapshot_time_id = MIN(snapshot_time_id) FROM core.snapshots WHERE snapshot_time >= @start_time;

    DECLARE @interval_sec int;
    SET @interval_sec = DATEDIFF (s, @start_time, @end_time);

    -- SQL and plan handles are passed in as a hex-formatted string. Convert to varbinary. 
    DECLARE @sql_handle varbinary(64), @plan_handle varbinary(64)
    SET @sql_handle = snapshots.fn_hexstrtovarbin (@sql_handle_str)
    IF LEN (@plan_handle_str) > 0
    BEGIN
        SET @plan_handle = snapshots.fn_hexstrtovarbin (@plan_handle_str)
    END

    SELECT 
        t.*, 
        master.dbo.fn_varbintohexstr (t.sql_handle) AS sql_handle_str, 
        master.dbo.fn_varbintohexstr (t.plan_handle) AS plan_handle_str
    FROM 
    (
        SELECT 
            stat.sql_handle, stat.statement_start_offset, stat.statement_end_offset, stat.plan_handle, 
            CONVERT (datetime, SWITCHOFFSET (CAST (stat.creation_time AS datetimeoffset(7)), '+00:00')) AS creation_time, 
            CONVERT (varchar, CONVERT (datetime, SWITCHOFFSET (CAST (MAX (stat.creation_time) AS datetimeoffset(7)), '+00:00')), 126) AS creation_time_str, 
            CONVERT (datetime, SWITCHOFFSET (CAST (MAX (stat.last_execution_time) AS datetimeoffset(7)), '+00:00')) AS last_execution_time, 
            snap.source_id, 
            SUM (stat.snapshot_execution_count) AS execution_count, 
            SUM (stat.snapshot_execution_count) / (@interval_sec / 60) AS executions_per_min, 
            SUM (stat.snapshot_worker_time / 1000) AS total_cpu, 
            SUM (stat.snapshot_worker_time / 1000) / @interval_sec AS avg_cpu_per_sec, 
            SUM (stat.snapshot_worker_time / 1000.0) / CASE SUM (stat.snapshot_execution_count) WHEN 0 THEN 1 ELSE SUM (stat.snapshot_execution_count) END AS avg_cpu_per_exec, 
            SUM (stat.snapshot_physical_reads) AS total_physical_reads, 
            SUM (stat.snapshot_physical_reads) / @interval_sec AS avg_physical_reads_per_sec, 
            SUM (stat.snapshot_physical_reads) / CASE SUM (stat.snapshot_execution_count) WHEN 0 THEN 1 ELSE SUM (stat.snapshot_execution_count) END AS avg_physical_reads_per_exec, 
            SUM (stat.snapshot_logical_writes) AS total_logical_writes, 
            SUM (stat.snapshot_logical_writes) / @interval_sec AS avg_logical_writes_per_sec, 
            SUM (stat.snapshot_logical_writes) / CASE SUM (stat.snapshot_execution_count) WHEN 0 THEN 1 ELSE SUM (stat.snapshot_execution_count) END AS avg_logical_writes_per_exec, 
            SUM (stat.snapshot_elapsed_time / 1000) AS total_elapsed_time, 
            SUM (stat.snapshot_elapsed_time / 1000) / @interval_sec AS avg_elapsed_time_per_sec, 
            SUM (stat.snapshot_elapsed_time / 1000.0) / CASE SUM (stat.snapshot_execution_count) WHEN 0 THEN 1 ELSE SUM (stat.snapshot_execution_count) END AS avg_elapsed_time_per_exec, 
            COUNT(*) AS row_count, COUNT (DISTINCT stat.creation_time) AS plan_count, 
            SUM (charted_value) AS charted_value, 
            ROW_NUMBER() OVER (ORDER BY SUM (charted_value) DESC) as query_rank
        FROM 
        (
            SELECT *, 
				-- This is the criteria used to rank the returned rowset and determine the order within Top-N plans
				-- returned from here. It is important that this part of the query stays in sync with a similar
				-- part of the query in snapshots.rpt_query_plan_stats_timeline procedure
                CASE @order_by_criteria     
                    WHEN 'CPU' THEN ((snapshot_worker_time / 1000.0) / CASE snapshot_execution_count WHEN 0 THEN 1 ELSE snapshot_execution_count END)
                    WHEN 'Physical Reads' THEN 1.0 * (snapshot_physical_reads / CASE snapshot_execution_count WHEN 0 THEN 1 ELSE snapshot_execution_count END)
                    WHEN 'Logical Writes' THEN 1.0 * (snapshot_logical_writes / CASE snapshot_execution_count WHEN 0 THEN 1 ELSE snapshot_execution_count END)
                    WHEN 'I/O' THEN 1.0 * ((snapshot_physical_reads + snapshot_logical_writes) / CASE snapshot_execution_count WHEN 0 THEN 1 ELSE snapshot_execution_count END)
                    WHEN 'Duration' THEN ((snapshot_elapsed_time / 1000.0) / CASE snapshot_execution_count WHEN 0 THEN 1 ELSE snapshot_execution_count END)
                    ELSE ((snapshot_worker_time / 1000.0) / CASE snapshot_execution_count WHEN 0 THEN 1 ELSE snapshot_execution_count END)
                END AS charted_value
            FROM snapshots.query_stats 
            WHERE 
                sql_handle = @sql_handle 
                AND (@plan_handle IS NULL OR plan_handle = @plan_handle) 
                AND (@plan_creation_time IS NULL OR creation_time = @plan_creation_time) 
                AND statement_start_offset = @statement_start_offset 
                AND statement_end_offset = @statement_end_offset
        ) stat
        INNER JOIN core.snapshots snap ON stat.snapshot_id = snap.snapshot_id
        WHERE
            snap.instance_name = @instance_name 
            AND snap.snapshot_time_id BETWEEN @start_snapshot_time_id AND @end_snapshot_time_id
        GROUP BY stat.sql_handle, stat.statement_start_offset, stat.statement_end_offset, stat.plan_handle, 
                stat.creation_time, snap.source_id
    ) t
    WHERE 
        (query_rank <= 10)
    ORDER BY query_rank ASC
    -- These trace flags are necessary for a good plan, due to the join on ascending PK w/range filter
    OPTION (QUERYTRACEON 2389, QUERYTRACEON 2390)
END
GO



--
-- snapshots.rpt_query_plan_stats_timeline
--  Returns stats for the top 10 plans observed for a query within a specified time interval. 
--  Output is intended for plotting this over a time window. 
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @start_time - (Optional) time window start (UTC)
--    @end_time - time window end (UTC)
--    @time_window_size - Number of intervals in the time window (provide if @start_time is NULL)
--    @time_interval_min - Number of minutes in each interval (provide if @start_time is NULL)
--    @sql_handle_str - String representation of a SQL handle (e.g. "0x1F27BC...")
--    @plan_handle_str - (Optional) String representation of a plan handle (e.g. "0x1F27BC..."). Omit to see stats for all plans
--    @plan_creation_time - (Optional) Plan creation time
--    @statement_start_offset - Start offset (byte count) for the statement within the batch identified by @sql_handle_str
--    @statement_start_offset - End offset (byte count) for the statement within the batch identified by @sql_handle_str
--    @order_by_criteria - (Optional) 'CPU' (default), 'Physical Reads', 'Logical Writes', 'I/O' (reads+writes), or 'Duration'
--
IF (NOT OBJECT_ID(N'snapshots.rpt_query_plan_stats_timeline', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[rpt_query_plan_stats_timeline] ...'
    DROP PROCEDURE [snapshots].[rpt_query_plan_stats_timeline]
END
GO 

PRINT 'Creating procedure [snapshots].[rpt_query_plan_stats_timeline] ...'
GO
CREATE PROCEDURE [snapshots].[rpt_query_plan_stats_timeline]
    @instance_name sysname,
    @start_time datetime = NULL,
    @end_time datetime = NULL,
    @time_window_size smallint,
    @time_interval_min smallint = 1, 
    @sql_handle_str varchar(130), 
    @plan_handle_str varchar(130) = NULL, 
    @plan_creation_time datetime = NULL, 
    @statement_start_offset int, 
    @statement_end_offset int, 
    @order_by_criteria varchar(30) = 'CPU'
AS
BEGIN
    SET NOCOUNT ON;

    -- @end_time should never be NULL when we are called from the Query Stats report
    -- Convert snapshot_time (datetimeoffset) to a UTC datetime
    IF (@end_time IS NULL)
        SET @end_time = CONVERT (datetime, SWITCHOFFSET (CAST ((SELECT MAX(snapshot_time) FROM core.snapshots) AS datetimeoffset(7)), '+00:00'));

    IF (@start_time IS NULL)
    BEGIN
        -- If time_window_size and time_interval_min are set use them
        -- to determine the start time
        -- Otherwise use the earliest available snapshot_time
        IF @time_window_size IS NOT NULL AND @time_interval_min IS NOT NULL
        BEGIN
            SET @start_time = DATEADD(minute, @time_window_size * @time_interval_min * -1.0, @end_time);
        END
        ELSE
        BEGIN
            -- Convert min snapshot_time (datetimeoffset) to a UTC datetime
            SET @start_time = CONVERT (datetime, SWITCHOFFSET (CAST ((SELECT MIN(snapshot_time) FROM core.snapshots) AS datetimeoffset(7)), '+00:00'));
        END
    END

    DECLARE @end_snapshot_time_id int;
    SELECT @end_snapshot_time_id = MAX(snapshot_time_id) FROM core.snapshots WHERE snapshot_time <= @end_time;

    DECLARE @start_snapshot_time_id int;
    SELECT @start_snapshot_time_id = MIN(snapshot_time_id) FROM core.snapshots WHERE snapshot_time >= @start_time;

    -- SQL and plan handles are passed in as a hex-formatted string. Convert to varbinary. 
    DECLARE @sql_handle varbinary(64), @plan_handle varbinary(64)
    SET @sql_handle = snapshots.fn_hexstrtovarbin (@sql_handle_str)
    IF LEN (ISNULL (@plan_handle_str, '')) > 0
    BEGIN
        SET @plan_handle = snapshots.fn_hexstrtovarbin (@plan_handle_str)
    END


    CREATE TABLE #top_plans (
        plan_handle varbinary(64), 
        creation_time datetimeoffset(7), 
        plan_rank int 
    )

    -- If we weren't told to focus on a particular plan...
    IF (@plan_handle IS NULL)
    BEGIN
        -- Get the top 10 most expensive plans for this query during the specified 
        -- time window. 
        INSERT INTO #top_plans
        SELECT * FROM
        (
            SELECT 
                plan_handle, 
                creation_time, 
                ROW_NUMBER() OVER (ORDER BY SUM (ranking_value) DESC) AS plan_rank 
            FROM 
            (
                SELECT *, 
				-- This is the criteria used to rank the returned rowset and determine the order within Top-N plans
				-- returned from here. It is important that this part of the query stays in sync with a similar
				-- part of the query in snapshots.rpt_query_plan_stats procedure
                CASE @order_by_criteria 
                    WHEN 'CPU' THEN ((snapshot_worker_time / 1000.0) / CASE snapshot_execution_count WHEN 0 THEN 1 ELSE snapshot_execution_count END)
                    WHEN 'Physical Reads' THEN 1.0 * (snapshot_physical_reads / CASE snapshot_execution_count WHEN 0 THEN 1 ELSE snapshot_execution_count END)
                    WHEN 'Logical Writes' THEN 1.0 * (snapshot_logical_writes / CASE snapshot_execution_count WHEN 0 THEN 1 ELSE snapshot_execution_count END)
                    WHEN 'I/O' THEN 1.0 * ((snapshot_physical_reads + snapshot_logical_writes) / CASE snapshot_execution_count WHEN 0 THEN 1 ELSE snapshot_execution_count END)
                    WHEN 'Duration' THEN ((snapshot_elapsed_time / 1000.0) / CASE snapshot_execution_count WHEN 0 THEN 1 ELSE snapshot_execution_count END)
                    ELSE ((snapshot_worker_time / 1000.0) / CASE snapshot_execution_count WHEN 0 THEN 1 ELSE snapshot_execution_count END)
                END AS ranking_value
            FROM snapshots.query_stats 
            WHERE 
                sql_handle = @sql_handle 
                AND statement_start_offset = @statement_start_offset 
                AND statement_end_offset = @statement_end_offset
        ) AS stat
        INNER JOIN core.snapshots snap ON stat.snapshot_id = snap.snapshot_id
        WHERE
            snap.instance_name = @instance_name 
            AND snap.snapshot_time_id BETWEEN @start_snapshot_time_id AND @end_snapshot_time_id
        GROUP BY plan_handle, creation_time
    ) AS t
    WHERE t.plan_rank <= 10
    -- These trace flags are necessary for a good plan, due to the join on ascending PK w/range filter
    OPTION (QUERYTRACEON 2389, QUERYTRACEON 2390);
    END
    ELSE 
    BEGIN
        -- @plan_handle is not NULL; we have been told to focus on a particular plan. 
        INSERT INTO #top_plans 
        VALUES (@plan_handle, @plan_creation_time, 1)
    END;

    -- Get statistics for these 10 plans for each collection point in the time window
    WITH raw_stat AS 
    (
        SELECT *, 
            CASE @order_by_criteria 
                WHEN 'CPU' THEN snapshot_worker_time / 1000.0
                WHEN 'Physical Reads' THEN snapshot_physical_reads 
                WHEN 'Logical Writes' THEN snapshot_logical_writes 
                WHEN 'I/O' THEN (snapshot_logical_writes + snapshot_physical_reads)
                WHEN 'Duration' THEN snapshot_elapsed_time / 1000.0
                ELSE snapshot_worker_time / 1000.0
            END AS charted_value
        FROM snapshots.query_stats AS stat
    )
    SELECT 
        t.*, 
        master.dbo.fn_varbintohexstr (t.sql_handle) AS sql_handle_str, 
        master.dbo.fn_varbintohexstr (t.plan_handle) AS plan_handle_str
    FROM 
    (
        SELECT 
            stat.sql_handle, stat.statement_start_offset, stat.statement_end_offset, stat.plan_handle, 
            CONVERT (datetime, SWITCHOFFSET (CAST (stat.creation_time AS datetimeoffset(7)), '+00:00')) AS creation_time, 
            CONVERT (varchar, CONVERT (datetime, SWITCHOFFSET (CAST (MAX (stat.creation_time) AS datetimeoffset(7)), '+00:00')), 126) AS creation_time_str,             
            CONVERT (datetime, SWITCHOFFSET (CAST (MAX (stat.last_execution_time) AS datetimeoffset(7)), '+00:00')) AS last_execution_time, 
            CONVERT (datetime, SWITCHOFFSET (CAST (stat.collection_time_chart AS datetimeoffset(7)), '+00:00')) AS collection_time, 
            snap.source_id, 
            SUM (stat.snapshot_execution_count) AS execution_count, 
            SUM (stat.snapshot_worker_time / 1000) AS total_cpu, 
            SUM (stat.snapshot_worker_time / 1000) / CASE SUM (stat.snapshot_execution_count) WHEN 0 THEN 1 ELSE SUM (stat.snapshot_execution_count) END AS avg_cpu_per_exec, 
            SUM (stat.snapshot_physical_reads) AS total_physical_reads, 
            SUM (stat.snapshot_physical_reads) / CASE SUM (stat.snapshot_execution_count) WHEN 0 THEN 1 ELSE SUM (stat.snapshot_execution_count) END AS avg_physical_reads_per_exec, 
            SUM (stat.snapshot_logical_writes) AS total_logical_writes, 
            SUM (stat.snapshot_logical_writes) / CASE SUM (stat.snapshot_execution_count) WHEN 0 THEN 1 ELSE SUM (stat.snapshot_execution_count) END AS avg_logical_writes_per_exec, 
            SUM (stat.snapshot_elapsed_time / 1000) AS total_elapsed_time, 
            SUM (stat.snapshot_elapsed_time / 1000) / CASE SUM (stat.snapshot_execution_count) WHEN 0 THEN 1 ELSE SUM (stat.snapshot_execution_count) END AS avg_elapsed_time_per_exec, 
            COUNT(*) AS row_count, COUNT (DISTINCT stat.creation_time) AS plan_count, 
            SUM (stat.charted_value) AS charted_value, 
            SUM (stat.charted_value) / CASE SUM (stat.snapshot_execution_count) WHEN 0 THEN 1 ELSE SUM (stat.snapshot_execution_count) END AS charted_value_per_exec, 
            MAX (topN.plan_rank) AS plan_rank -- same value for all rows within a group
        FROM (
            -- Work around a RS chart limitation (single data points do not plot on line charts). 
            -- Fake a second data point shortly after the first so even short-lived plans will 
            -- gets plotted. 
            SELECT *, collection_time AS collection_time_chart FROM raw_stat
            UNION ALL
            SELECT *, DATEADD (mi, 1, collection_time) AS collection_time_chart FROM raw_stat
        ) AS stat
        INNER JOIN #top_plans AS topN 
            ON topN.plan_handle = stat.plan_handle AND topN.creation_time = stat.creation_time
        INNER JOIN core.snapshots snap ON stat.snapshot_id = snap.snapshot_id
        WHERE
            stat.sql_handle = @sql_handle 
            AND statement_start_offset = @statement_start_offset 
            AND statement_end_offset = @statement_end_offset
            AND snap.instance_name = @instance_name 
            AND snap.snapshot_time_id BETWEEN @start_snapshot_time_id AND @end_snapshot_time_id
        GROUP BY stat.sql_handle, stat.statement_start_offset, stat.statement_end_offset, stat.plan_handle, 
                stat.creation_time, snap.source_id, stat.collection_time_chart
    ) AS t
    WHERE 
        (plan_rank <= 10)
    ORDER BY plan_rank ASC, collection_time ASC
    -- These trace flags are necessary for a good plan, due to the join on ascending PK w/range filter
    OPTION (QUERYTRACEON 2389, QUERYTRACEON 2390)
END
GO


--
-- snapshots.rpt_query_plan_missing_indexes
--  Returns any missing indexes recorded in a query plan, formatted as CREATE INDEX statements. 
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @plan_handle_str - String representation of a plan handle (e.g. "0x1F27BC...")
--    @statement_start_offset - Start offset (byte count) for the statement within the plan identified by @plan_handle_str
--    @statement_start_offset - End offset (byte count) for the statement within the plan identified by @plan_handle_str
--
IF (NOT OBJECT_ID(N'snapshots.rpt_query_plan_missing_indexes', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[rpt_query_plan_missing_indexes] ...'
    DROP PROCEDURE [snapshots].[rpt_query_plan_missing_indexes]
END
GO 

PRINT 'Creating procedure [snapshots].[rpt_query_plan_missing_indexes] ...'
GO
CREATE PROCEDURE [snapshots].[rpt_query_plan_missing_indexes]
    @instance_name sysname, 
    @plan_handle_str varchar(130), 
    @statement_start_offset int, 
    @statement_end_offset int 
AS
BEGIN
    SET NOCOUNT ON;
    DECLARE @showplan nvarchar(max)
    DECLARE @plan_handle varbinary(64)
    DECLARE @element nvarchar(512), @element_start nvarchar(512), @element_end nvarchar(512)
    DECLARE @xml_fragment xml
    DECLARE @start_offset int

    -- We may be invoked with a NULL/empty string plan handle if the user has not yet drilled down into 
    -- a specific query plan on the query_stats_detail report. 
    IF ISNULL (@plan_handle_str, '') = '' 
    BEGIN
        RETURN
    END

    SET @plan_handle = snapshots.fn_hexstrtovarbin (@plan_handle_str)

    SELECT TOP 1 
        @showplan = CONVERT (nvarchar(max), qp.query_plan) 
    FROM snapshots.notable_query_plan qp
    INNER JOIN core.snapshots snap ON snap.source_id = qp.source_id 
    WHERE plan_handle = @plan_handle 
        AND statement_start_offset = @statement_start_offset AND statement_end_offset = @statement_end_offset
        AND snap.instance_name = @instance_name
        -- Get sql_handle to enable a clustered index seek on notable_query_plans
        AND qp.sql_handle = 
        (
            SELECT TOP 1 sql_handle 
            FROM snapshots.notable_query_plan AS qp
            INNER JOIN core.snapshots snap ON snap.source_id = qp.source_id 
            WHERE plan_handle = @plan_handle
                AND statement_start_offset = @statement_start_offset AND statement_end_offset = @statement_end_offset 
                AND snap.instance_name = @instance_name
        );

    SET @element = 'MissingIndexes'
    -- Use non-XML methods to extract the <MissingIndexes> fragment.  Some query plans may be too complex 
    -- to represent in the T-SQL xml data type, but the <MissingIndexes> node will always be simple enough. 
    -- Doing this ensures that we will always be able to extract any missing index information from the 
    -- plan even if the plan itself is too complex for the T-SQL xml type. 
    SET @element_start = '<' + @element + '>'
    SET @element_end = '</' + @element + '>'
    SET @start_offset = ISNULL (PATINDEX ('%' + @element_start + '%', @showplan), 0)
    IF @start_offset > 0 
    BEGIN
        SET @xml_fragment = SUBSTRING (@showplan, @start_offset, PATINDEX ('%' + @element_end + '%', @showplan) - @start_offset + LEN (@element_end)); 

        --  Sample <MissingIndexes> fragment from an XML query plan: 
        --    <MissingIndexes>
        --      <MissingIndexGroup Impact="26.4126">
        --        <MissingIndex Database="[AdventureWorks]" Schema="[Sales]" Table="[CustomerAddress]">
        --          <ColumnGroup Usage="EQUALITY">
        --            <Column Name="[AddressTypeID]" ColumnId="3"/>
        --          </ColumnGroup>
        --          <ColumnGroup Usage="INCLUDE">
        --          <Column Name="[CustomerID]" ColumnId="1"/>
        --            <Column Name="[AddressID]" ColumnId="2"/>
        --          </ColumnGroup>
        --        </MissingIndex>
        --      </MissingIndexGroup>
        --    </MissingIndexes>
        SELECT 
            'CREATE INDEX [ncidx_mdw_' 
                + LEFT (target_object_name, 20)
                -- Random component to make name conflicts less likely
                + '_' + CONVERT (varchar(30), ABS (CONVERT (binary(6), NEWID()) % 1000))
                + '] ON ' + target_object_fullname 
                + ' (' + ISNULL (equality_columns, '')
                + CASE WHEN ISNULL (equality_columns, '') != '' AND ISNULL (inequality_columns, '') != '' THEN ',' ELSE '' END + ISNULL (inequality_columns, '')
                + ')'
                + CASE WHEN ISNULL (included_columns, '') != '' THEN ' INCLUDE (' + included_columns + ')' ELSE '' END
                AS create_idx_statement, 
            *
        FROM 
        (
            SELECT 
                index_node.value('(../@Impact)[1]', 'float') as index_impact,
                REPLACE (REPLACE (index_node.value('(./@Table)[1]', 'nvarchar(512)'), '[', ''), ']', '') AS target_object_name,
                CONVERT (nvarchar(1024), index_node.query('concat(
		                string((./@Database)[1]), 
		                ".",
		                string((./@Schema)[1]),
		                ".",
		                string((./@Table)[1])
	                )')) AS target_object_fullname,
                REPLACE (CONVERT (nvarchar(max), index_node.query('for $colgroup in ./ColumnGroup,
		                $col in $colgroup/Column
		                where $colgroup/@Usage = "EQUALITY"
		                return string($col/@Name)')), '] [', '],[') AS equality_columns,
                REPLACE (CONVERT (nvarchar(max), index_node.query('for $colgroup in ./ColumnGroup,
		                $col in $colgroup/Column
		                where $colgroup/@Usage = "INEQUALITY"
		                return string($col/@Name)')), '] [', '],[') AS inequality_columns,
                REPLACE (CONVERT (nvarchar(max), index_node.query('for $colgroup in .//ColumnGroup,
		                $col in $colgroup/Column
		                where $colgroup/@Usage = "INCLUDE"
		                return string($col/@Name)')), '] [', '],[') AS included_columns
            FROM (SELECT @xml_fragment AS fragment) AS missing_indexes_fragment
            CROSS APPLY missing_indexes_fragment.fragment.nodes('/MissingIndexes/MissingIndexGroup/MissingIndex') AS missing_indexes (index_node)
        ) AS t
    END
END
GO

--
-- snapshots.rpt_query_plan_parameters
--  Returns the compile-time parameters that a query plan was optimized for
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @plan_handle_str - String representation of a plan handle (e.g. "0x1F27BC...")
--    @statement_start_offset - Start offset (byte count) for the statement within the plan identified by @plan_handle_str
--    @statement_start_offset - End offset (byte count) for the statement within the plan identified by @plan_handle_str
--
IF (NOT OBJECT_ID(N'snapshots.rpt_query_plan_parameters', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[rpt_query_plan_parameters] ...'
    DROP PROCEDURE [snapshots].rpt_query_plan_parameters
END
GO 

PRINT 'Creating procedure [snapshots].[rpt_query_plan_parameters] ...'
GO
CREATE PROCEDURE [snapshots].rpt_query_plan_parameters
    @instance_name sysname, 
    @plan_handle_str varchar(130), 
    @statement_start_offset int, 
    @statement_end_offset int 
AS
BEGIN
    SET NOCOUNT ON;
    DECLARE @showplan xml
    DECLARE @plan_handle varbinary(64)

    -- We may be invoked with a NULL/empty string plan handle if the user has not yet drilled down into 
    -- a specific query plan on the query_stats_detail report. 
    IF ISNULL (@plan_handle_str, '') = '' 
    BEGIN
        RETURN
    END

    SET @plan_handle = snapshots.fn_hexstrtovarbin (@plan_handle_str)

    BEGIN TRY
        SELECT TOP 1 
            @showplan = CONVERT (xml, qp.query_plan) 
        FROM snapshots.notable_query_plan qp
        INNER JOIN core.snapshots snap ON snap.source_id = qp.source_id 
        WHERE plan_handle = @plan_handle 
            AND statement_start_offset = @statement_start_offset AND statement_end_offset = @statement_end_offset 
            AND snap.instance_name = @instance_name
            -- Get sql_handle to enable a clustered index seek on notable_query_plans
            AND qp.sql_handle = 
            (
                SELECT TOP 1 sql_handle 
                FROM snapshots.notable_query_plan AS qp
                INNER JOIN core.snapshots snap ON snap.source_id = qp.source_id 
                WHERE plan_handle = @plan_handle
                    AND statement_start_offset = @statement_start_offset AND statement_end_offset = @statement_end_offset 
                    AND snap.instance_name = @instance_name
            );
    END TRY
    BEGIN CATCH
        -- It is expected that we may end up here even under normal circumstances.  Some plans are simply too 
        -- complex to represent using T-SQL's xml datatype. Raise a low-severity message with the error details. 
        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE();
        -- "Unable to convert showplan to XML.  Error #%d on Line %d: %s"
        RAISERROR (14697, 0, 1, @ErrorNumber, @ErrorLine, @ErrorMessage);
    END CATCH;

    WITH XMLNAMESPACES ('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS sp)
    SELECT 
        param_list.param_node.value('(./@Column)[1]', 'nvarchar(512)') AS param_name,
        param_list.param_node.value('(./@ParameterCompiledValue)[1]', 'nvarchar(max)') AS param_compiled_value 
    FROM (SELECT @showplan AS query_plan) AS p
    CROSS APPLY p.query_plan.nodes ('/sp:ShowPlanXML/sp:BatchSequence/sp:Batch/sp:Statements/sp:StmtSimple/sp:QueryPlan[1]/sp:ParameterList[1]/sp:ColumnReference') as param_list (param_node)
END
GO


--
-- snapshots.rpt_query_plan_details
--  Returns details of a query plan (estimated cost, compile-time CPU, etc)
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @plan_handle_str - String representation of a plan handle (e.g. "0x1F27BC...")
--    @statement_start_offset - Start offset (byte count) for the statement within the plan identified by @plan_handle_str
--    @statement_start_offset - End offset (byte count) for the statement within the plan identified by @plan_handle_str
--
IF (NOT OBJECT_ID(N'snapshots.rpt_query_plan_details', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[rpt_query_plan_details] ...'
    DROP PROCEDURE [snapshots].rpt_query_plan_details
END
GO 

PRINT 'Creating procedure [snapshots].[rpt_query_plan_details] ...'
GO
CREATE PROCEDURE [snapshots].rpt_query_plan_details
    @instance_name sysname, 
    @plan_handle_str varchar(130), 
    @statement_start_offset int, 
    @statement_end_offset int 
AS
BEGIN
    SET NOCOUNT ON;
    DECLARE @showplan xml
    DECLARE @plan_handle varbinary(64)

    -- We may be invoked with a NULL/empty string plan handle if the user has not yet drilled down into 
    -- a specific query plan on the query_stats_detail report. 
    IF ISNULL (@plan_handle_str, '') = '' 
    BEGIN
        RETURN
    END

    SET @plan_handle = snapshots.fn_hexstrtovarbin (@plan_handle_str)

    BEGIN TRY
        SELECT TOP 1 
            @showplan = CONVERT (xml, qp.query_plan) 
        FROM snapshots.notable_query_plan AS qp 
        INNER JOIN core.snapshots snap ON snap.source_id = qp.source_id 
        WHERE plan_handle = @plan_handle 
            AND statement_start_offset = @statement_start_offset AND statement_end_offset = @statement_end_offset 
            AND snap.instance_name = @instance_name 
            -- Get sql_handle to enable a clustered index seek on notable_query_plans
            AND qp.sql_handle = 
            (
                SELECT TOP 1 sql_handle 
                FROM snapshots.notable_query_plan AS qp
                INNER JOIN core.snapshots snap ON snap.source_id = qp.source_id 
                WHERE plan_handle = @plan_handle
                    AND statement_start_offset = @statement_start_offset AND statement_end_offset = @statement_end_offset 
                    AND snap.instance_name = @instance_name
            );
    END TRY
    BEGIN CATCH
        -- It is expected that we may end up here even under normal circumstances.  Some plans are simply too 
        -- complex to represent using T-SQL's xml datatype. Raise a low-severity message with the error details. 
        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE();
        -- "Unable to convert showplan to XML.  Error #%d on Line %d: %s"
        RAISERROR (14697, 0, 1, @ErrorNumber, @ErrorLine, @ErrorMessage);
        RETURN
    END CATCH;

    WITH XMLNAMESPACES ('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS sp)
    SELECT TOP 10 
        CONVERT (bigint, stmt_simple.stmt_node.value('(./@StatementEstRows)[1]', 'decimal(28,10)')) AS stmt_est_rows, 
        stmt_simple.stmt_node.value('(./@StatementOptmLevel)[1]', 'varchar(30)') AS stmt_optimization_level, 
        stmt_simple.stmt_node.value('(./@StatementOptmEarlyAbortReason)[1]', 'varchar(30)') AS stmt_optimization_early_abort_reason, 
        stmt_simple.stmt_node.value('(./@StatementSubTreeCost)[1]', 'float') AS stmt_est_subtree_cost, 
        stmt_simple.stmt_node.value('(./@StatementText)[1]', 'nvarchar(max)') AS stmt_text, 
        stmt_simple.stmt_node.value('(./@ParameterizedText)[1]', 'nvarchar(max)') AS stmt_parameterized_text, 
        stmt_simple.stmt_node.value('(./@StatementType)[1]', 'varchar(30)') AS stmt_type, 
        stmt_simple.stmt_node.value('(./@PlanGuideName)[1]', 'varchar(30)') AS plan_guide_name, 
        stmt_simple.stmt_node.value('(./sp:QueryPlan/@CachedPlanSize)[1]', 'int') AS plan_size, 
        stmt_simple.stmt_node.value('(./sp:QueryPlan/@CompileTime)[1]', 'bigint') AS plan_compile_time, 
        stmt_simple.stmt_node.value('(./sp:QueryPlan/@CompileCPU)[1]', 'bigint') AS plan_compile_cpu, 
        stmt_simple.stmt_node.value('(./sp:QueryPlan/@CompileMemory)[1]', 'int') AS plan_compile_memory 
    FROM (SELECT @showplan AS query_plan) AS p
    CROSS APPLY p.query_plan.nodes ('/sp:ShowPlanXML/sp:BatchSequence/sp:Batch/sp:Statements/sp:StmtSimple') as stmt_simple (stmt_node)
END
GO


--
-- snapshots.rpt_blocking_chains
--  Returns summary of blocking chains that existed in a specified time window
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @start_time - time window start (UTC)
--    @end_time - time window end (UTC)
--
IF (NOT OBJECT_ID(N'snapshots.rpt_blocking_chains', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[rpt_blocking_chains] ...'
    DROP PROCEDURE [snapshots].[rpt_blocking_chains]
END
GO 

PRINT 'Creating procedure [snapshots].[rpt_blocking_chains] ...'
GO
CREATE PROCEDURE [snapshots].[rpt_blocking_chains]
    @instance_name sysname,
    @start_time datetime = NULL, 
    @end_time datetime, 
    @WindowSize int = NULL
AS
BEGIN
SET NOCOUNT ON;

    -- Compensate for RS truncation of fractional seconds
    SET @end_time = DATEADD (second, 1, @end_time) 

    -- If @start_time is NULL, calc it using @end_time and @WindowSize
    IF @start_time IS NULL SET @start_time = DATEADD (minute, -1 * @WindowSize, @end_time)

    -- Get all collection times for the "Active Sessions and Requests" collection item
    SELECT DISTINCT r1.collection_time, r1.snapshot_id, 
        DENSE_RANK() OVER (ORDER BY r1.collection_time) AS collection_time_id
    INTO #collection_times
    FROM snapshots.active_sessions_and_requests AS r1 
    INNER JOIN core.snapshots s ON s.snapshot_id = r1.snapshot_id 
    WHERE 
        s.instance_name = @instance_name 
        AND r1.collection_time BETWEEN @start_time AND @end_time

    DECLARE @max_collection_time datetimeoffset(7)
    SELECT @max_collection_time = MAX (collection_time) FROM #collection_times

    -- Get all head blockers during the selected time window
    SELECT r1.*, times.collection_time_id
    INTO #blocking_participants 
    FROM #collection_times AS times
    LEFT OUTER JOIN snapshots.active_sessions_and_requests AS r1 
        ON r1.collection_time = times.collection_time AND r1.snapshot_id = times.snapshot_id
    WHERE r1.blocking_session_id = 0
        AND session_id IN (
            SELECT DISTINCT blocking_session_id 
            FROM snapshots.active_sessions_and_requests AS r2 
            WHERE r2.blocking_session_id != 0 AND r2.collection_time = r1.collection_time
                AND r2.snapshot_id = r1.snapshot_id
        )
    CREATE NONCLUSTERED INDEX IDX1_blocking_participants 
    ON #blocking_participants 
    (
        session_id, collection_time_id, blocking_session_id
    )

    -- List all blocking chains during this time window. 
    -- For the purposes of this overview, we define a blocking chain as a contiguous series 
    -- of samples where the same spid remains the head blocker.  Rolling blocking will be viewed 
    -- as multiple discrete chains. 
    SELECT 
        MIN (head_blockers.collection_time) AS blocking_start_time, 
        -- We know when the blocking ended within a (roughly) 10 second window. Assume it stopped approximately 
        -- at the midpoint. 
        DATEADD (second, 5, ISNULL (MAX (blocking_end_times.collection_time), @max_collection_time)) AS blocking_end_time, 
        DATEDIFF (second, MIN (head_blockers.collection_time), ISNULL (MAX (blocking_end_times.collection_time), @max_collection_time)) 
            AS blocking_duration_sec,
        head_blockers.session_id AS head_blocker_session_id, 
        MIN (head_blockers.[program_name]) AS [program_name], 
        MIN (head_blockers.[database_name]) AS [database_name], 
        COUNT(*) AS observed_sample_count,  -- Number of times we saw this blocking chain
        CASE WHEN MAX (blocking_end_times.collection_time) IS NULL THEN 1 ELSE 0 END AS still_active
    INTO #blocking_chains
    FROM 
    (
        SELECT 
            (   -- Find the end time for this blocking incident
                SELECT MIN (collection_time_id)
                FROM #collection_times AS times1
                WHERE times1.collection_time_id > blockers_start.collection_time_id 
                    AND times1.collection_time_id <= @end_time 
                    AND NOT EXISTS (
                        SELECT * FROM #blocking_participants AS blk1
                        WHERE blk1.session_id = blockers_start.session_id 
                            AND blk1.[program_name] = blockers_start.[program_name] 
                            AND blk1.login_time = blockers_start.login_time 
                            AND blk1.collection_time_id = times1.collection_time_id
                            AND blk1.blocking_session_id = 0
                    )
            ) AS blocking_end_collection_time_id, 
            *
        FROM #blocking_participants AS blockers_start 
        WHERE blockers_start.blocking_session_id = 0 
    ) AS head_blockers
    LEFT OUTER JOIN #collection_times AS blocking_end_times
        ON blocking_end_times.collection_time_id = head_blockers.blocking_end_collection_time_id - 1
    GROUP BY head_blockers.session_id, head_blockers.blocking_end_collection_time_id 
    ORDER BY MIN (head_blockers.collection_time)

    -- This proc supports two different chart elements: a table, with one row per blocking 
    -- chain, and a timeline chart, with one series per blocking chain.  The chart must 
    -- return two data points per series in order to correctly plot the blocking chain's 
    -- beginning and end times.  The chart uses the output of both of the following UNIONed 
    -- SELECT statements, while the table filters out the second resultset 
    -- ([chart_data_only]=0). Doing this avoids the need to waste time running two procs 
    -- that are almost identical.  
    SELECT 
        blocking_chain_number, 
        CONVERT (datetime, SWITCHOFFSET (CAST (blocking_start_time AS datetimeoffset(7)), '+00:00')) AS blocking_start_time, 
        CONVERT (datetime, SWITCHOFFSET (CAST (blocking_end_time AS datetimeoffset(7)), '+00:00')) AS blocking_end_time, 
        blocking_duration_sec, 
        head_blocker_session_id, 
        [program_name], 
        [database_name], 
        observed_sample_count, 
        still_active, 
        -- Represent this time as a string to avoid RS datetime truncation when the report passes it back to us on drillthrough
        CONVERT (varchar(40), SWITCHOFFSET (CAST (blocking_start_time AS datetimeoffset(7)), '+00:00'), 126) AS blocking_start_time_str, 
        CONVERT (datetime, SWITCHOFFSET (CAST (chart_time AS datetimeoffset(7)), '+00:00')) AS chart_time, 
        chart_data_only
    FROM 
    (
        SELECT TOP 10 ROW_NUMBER() OVER (ORDER BY blocking_duration_sec DESC) AS blocking_chain_number, 
            *, blocking_start_time AS chart_time, 
            0 AS chart_data_only 
        FROM #blocking_chains
        ORDER BY blocking_duration_sec DESC
        UNION ALL 
        SELECT TOP 10 ROW_NUMBER() OVER (ORDER BY blocking_duration_sec DESC) AS blocking_chain_number, 
            *, blocking_end_time AS chart_time, 
            1 AS chart_data_only 
        FROM #blocking_chains
        ORDER BY blocking_duration_sec DESC
    ) AS t
    ORDER BY blocking_chain_number, chart_data_only
END
GO


--
-- snapshots.rpt_blocking_chain_detail
--  Returns details about a blocking chain
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @blocking_time_str - a string representation of a datetimeoffset value during the 
--        period where @head_blocker_session_id was the cause of blocking (UTC)
--    @head_blocker_session_id - session ID (SPID) of the head of the blocker chain
--
IF (NOT OBJECT_ID(N'snapshots.rpt_blocking_chain_detail', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[rpt_blocking_chain_detail] ...'
    DROP PROCEDURE [snapshots].[rpt_blocking_chain_detail]
END
GO 

PRINT 'Creating procedure [snapshots].[rpt_blocking_chain_detail] ...'
GO
CREATE PROCEDURE [snapshots].[rpt_blocking_chain_detail]
    @instance_name sysname, 
    @blocking_time_str varchar(40), 
    @head_blocker_session_id int
AS
BEGIN
SET NOCOUNT ON;
    DECLARE @blocking_start_time datetimeoffset(7)
    DECLARE @blocking_end_time datetimeoffset(7)
    DECLARE @blocking_time datetimeoffset(7)
    -- The report passed in the blocking time as a string to avoid RS date truncation. 
    -- Convert this to a datetimeoffset value in UTC time.
    SET @blocking_time = SWITCHOFFSET (CAST (@blocking_time_str AS datetimeoffset(7)), '+00:00')

    -- The time that we were passed in may have been in the middle of the blocking incident. 
    -- Find the true start time for this blocking chain. This might be 10 seconds prior, or 
    -- might be days prior.  For perf reasons, search backwards in time one hour at a time 
    -- until we find the start of the blocking incident. 
    DECLARE @blocking_end_snapshot_id int
    DECLARE @blocking_end_source_id int
    DECLARE @hour_count int
    SET @hour_count = -1
    WHILE (@blocking_start_time IS NULL)
    BEGIN
        -- Only if we have more rows left to search for the blocking end time
        IF EXISTS (
            SELECT * 
            FROM snapshots.active_sessions_and_requests AS r 
            INNER JOIN core.snapshots AS s ON s.snapshot_id = r.snapshot_id
            WHERE s.instance_name = @instance_name 
                AND r.collection_time < DATEADD (hour, @hour_count+1, @blocking_time)
        )
        BEGIN
            SELECT TOP 1 @blocking_start_time = r.collection_time 
            FROM snapshots.active_sessions_and_requests AS r 
            INNER JOIN core.snapshots AS s ON s.snapshot_id = r.snapshot_id
            WHERE s.instance_name = @instance_name
                AND r.collection_time BETWEEN DATEADD (hour, @hour_count, @blocking_time) AND @blocking_time 
                AND NOT EXISTS 
                ( 
                    SELECT * 
                    FROM snapshots.active_sessions_and_requests AS r2 
                    WHERE r.snapshot_id = r2.snapshot_id AND r.collection_time = r2.collection_time 
                        AND r2.blocking_session_id = @head_blocker_session_id 
                ) 
            ORDER BY r.collection_time DESC
        END
        ELSE
        BEGIN
            -- We've reached the beginning of the data in the warehouse, and the blocking incident was already 
            -- in-progress at that time. Use the earliest collection time as the approx blocking start time. 
            SELECT @blocking_start_time = ISNULL (DATEADD (second, -10, MIN (r.collection_time)), GETUTCDATE())
            FROM snapshots.active_sessions_and_requests AS r 
            INNER JOIN core.snapshots AS s ON s.snapshot_id = r.snapshot_id
            WHERE s.instance_name = @instance_name
        END
        SET @hour_count = @hour_count - 1
    END
    -- We've found the collection_time just before the blocking began. Get the next collection time, 
    -- which is the first collection time where this blocking incident was detected. 
    SELECT TOP 1 @blocking_start_time = r.collection_time
    FROM snapshots.active_sessions_and_requests AS r 
    INNER JOIN core.snapshots AS s ON s.snapshot_id = r.snapshot_id
    WHERE s.instance_name = @instance_name AND r.collection_time > @blocking_start_time
    ORDER BY r.collection_time ASC

    -- Now find the end of the blocking incident.  Here, again, do an optimistic search in 1-hour blocks. 
    SET @hour_count = 1
    WHILE (@blocking_end_time IS NULL)
    BEGIN
        -- Only if we have more rows left to search for the blocking end time
        IF EXISTS (
            SELECT * 
            FROM snapshots.active_sessions_and_requests AS r 
            INNER JOIN core.snapshots AS s ON s.snapshot_id = r.snapshot_id
            WHERE s.instance_name = @instance_name 
                AND r.collection_time > DATEADD (hour, @hour_count-1, @blocking_time)
        )
        BEGIN
            SELECT TOP 1 @blocking_end_time = r.collection_time 
            FROM snapshots.active_sessions_and_requests AS r 
            INNER JOIN core.snapshots AS s ON s.snapshot_id = r.snapshot_id
            WHERE s.instance_name = @instance_name
                AND r.collection_time BETWEEN DATEADD (hour, @hour_count-1, @blocking_time) AND DATEADD (hour, @hour_count, @blocking_time)
                AND NOT EXISTS 
                ( 
                    SELECT * 
                    FROM snapshots.active_sessions_and_requests AS r2 
                    WHERE r.snapshot_id = r2.snapshot_id AND r.collection_time = r2.collection_time 
                        AND r2.blocking_session_id = @head_blocker_session_id 
                ) 
            ORDER BY r.collection_time ASC
        END
        ELSE
        BEGIN
            -- We've reached the end of the data in the warehouse, and the blocking incident is still 
            -- in-progress. Use the last collection time as the approx blocking end time. 
            SELECT @blocking_end_time = ISNULL (DATEADD (second, 10, MAX (r.collection_time)), GETUTCDATE())
            FROM snapshots.active_sessions_and_requests AS r 
            INNER JOIN core.snapshots AS s ON s.snapshot_id = r.snapshot_id
            WHERE s.instance_name = @instance_name
        END
        SET @hour_count = @hour_count + 1
    END
    -- We've found the collection_time just after before the blocking ended. Get the prior collection time, 
    -- which is the last collection time where the blocking incident was detected. 
    SELECT TOP 1 @blocking_end_time = r.collection_time, @blocking_end_snapshot_id = r.snapshot_id, @blocking_end_source_id = s.source_id
    FROM snapshots.active_sessions_and_requests AS r 
    INNER JOIN core.snapshots AS s ON s.snapshot_id = r.snapshot_id
    WHERE s.instance_name = @instance_name AND r.collection_time < @blocking_end_time
    ORDER BY r.collection_time DESC

    -- DC captures a snapshot of session state every few seconds, which would mean hundreds of samples for 
    -- moderately long-lived blocking chains.  It would be too expensive to summarize the state of the blocking 
    -- chain at every one of these points.  Instead, select 10 evenly-spaced intervals during the blocking 
    -- incident to characterize the changes in the head blocker's state over the blocking period. 
    DECLARE @interval_sec int
    SET @interval_sec = DATEDIFF (second, @blocking_start_time, @blocking_end_time) / 10
    CREATE TABLE #sample_collection_times (snapshot_id int, source_id int, collection_time datetimeoffset(7))
    DECLARE @i int
    SET @i = 0
    WHILE (@i < 9)
    BEGIN
        INSERT INTO #sample_collection_times 
        SELECT TOP 1 r.snapshot_id, s.source_id, r.collection_time
        FROM snapshots.active_sessions_and_requests AS r 
        INNER JOIN core.snapshots AS s ON s.snapshot_id = r.snapshot_id
        WHERE s.instance_name = @instance_name 
            AND r.collection_time BETWEEN DATEADD (second, @interval_sec * @i, @blocking_start_time)
                AND DATEADD (second, @interval_sec * (@i+1)-1, @blocking_start_time)
            -- Only choose collection times where we have info for the head blocker
            AND r.session_id = @head_blocker_session_id 
        ORDER BY r.collection_time ASC
        SET @i = @i + 1
    END
    -- The 10th sample time is always the blocking incident's final collection time
    INSERT INTO #sample_collection_times VALUES (@blocking_end_snapshot_id, @blocking_end_source_id, @blocking_end_time);

    -- Use a recursive CTE to walk the tree of the blocking chain at each of these collection times
    -- and get the state of all the sessions that were part of the tree
    WITH blocking_hierarchy AS  
    (
        -- Head blocker at each of the selected sample times
        SELECT t.collection_time, t.snapshot_id, t.source_id, 0 AS [level], 
            r.session_id, r.request_id, r.exec_context_id, r.request_status, r.command, 
            r.blocking_session_id, r.blocking_exec_context_id, 
            r.wait_type, r.wait_duration_ms, r.wait_resource, r.resource_description, 
            r.login_name, r.login_time, r.[program_name], r.[host_name], r.database_name, 
            r.open_transaction_count, r.transaction_isolation_level, 
            r.request_cpu_time, r.request_total_elapsed_time, r.request_start_time, r.memory_usage, 
            r.session_cpu_time, r.session_total_scheduled_time, r.session_row_count, r.pending_io_count, r.prev_error, 
            r.session_last_request_start_time, r.session_last_request_end_time, r.open_resultsets, 
            r.plan_handle, r.sql_handle, r.statement_start_offset, r.statement_end_offset 
        FROM #sample_collection_times AS t
        INNER JOIN snapshots.active_sessions_and_requests AS r
            ON t.snapshot_id = r.snapshot_id AND t.collection_time = r.collection_time
        WHERE r.session_id = @head_blocker_session_id 
            AND ISNULL (r.exec_context_id, 0) IN (-1, 0) -- for the head blocker, only return the main worker's state
        UNION ALL 
        -- Tasks blocked by the head blocker at the same times
        SELECT r2.collection_time, r2.snapshot_id, parent.source_id, parent.[level] + 1 AS [level], 
            r2.session_id, r2.request_id, r2.exec_context_id, r2.request_status, r2.command, 
            r2.blocking_session_id, r2.blocking_exec_context_id, 
            r2.wait_type, r2.wait_duration_ms, r2.wait_resource, r2.resource_description, 
            r2.login_name, r2.login_time, r2.[program_name], r2.[host_name], r2.database_name, 
            r2.open_transaction_count, r2.transaction_isolation_level, 
            r2.request_cpu_time, r2.request_total_elapsed_time, r2.request_start_time, r2.memory_usage, 
            r2.session_cpu_time, r2.session_total_scheduled_time, r2.session_row_count, r2.pending_io_count, r2.prev_error, 
            r2.session_last_request_start_time, r2.session_last_request_end_time, r2.open_resultsets, 
            r2.plan_handle, r2.sql_handle, r2.statement_start_offset, r2.statement_end_offset 
        FROM snapshots.active_sessions_and_requests AS r2
        INNER JOIN blocking_hierarchy AS parent 
            ON parent.snapshot_id = r2.snapshot_id AND parent.collection_time = r2.collection_time
                AND parent.session_id = r2.blocking_session_id
    )
    SELECT * INTO #blocking_hierarchy 
    FROM blocking_hierarchy 
    
    -- Summarize the state of the head blocker at each of the sample times, along with 
    -- some aggregate stats (# of blocked sessions, etc) describing the blocked sessions
    SELECT 
        CONVERT (datetime, SWITCHOFFSET (CAST (@blocking_start_time AS datetimeoffset(7)), '+00:00')) AS blocking_start_time, 
        CONVERT (datetime, SWITCHOFFSET (CAST (@blocking_end_time AS datetimeoffset(7)), '+00:00')) AS blocking_end_time, 
        DATEDIFF (second, @blocking_start_time, @blocking_end_time) AS blocking_duration, 
        -- Return these dates as strings to avoid RS date truncation
        CONVERT (varchar(40), SWITCHOFFSET (CAST (@blocking_start_time AS datetimeoffset(7)), '+00:00'), 126) AS blocking_start_time_str, 
        CONVERT (varchar(40), SWITCHOFFSET (CAST (@blocking_end_time AS datetimeoffset(7)), '+00:00'), 126) AS blocking_end_time_str, 
        CONVERT (datetime, SWITCHOFFSET (CAST (blocked.chart_collection_time AS datetimeoffset(7)), '+00:00')) AS chart_collection_time, 
        blocked.chart_only, 
        CONVERT (datetime, SWITCHOFFSET (CAST (blocked.collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        blocked.snapshot_id, 
        CONVERT (varchar(40), SWITCHOFFSET (CAST (blocked.collection_time AS datetimeoffset(7)), '+00:00'), 126) AS collection_time_str, 
        @head_blocker_session_id AS head_blocker_session_id, 
        COUNT(DISTINCT blocked.session_id) AS blocked_session_count, 
        SUM (blocked.wait_duration_ms) AS total_wait_time, 
        AVG (blocked.wait_duration_ms) AS avg_wait_time, 
        (
            -- Get the description of the resource owned by the head blocker that 
            -- has caused the most wait time in this blocking chain
            SELECT TOP 1 resource_description
            FROM #blocking_hierarchy bh
            WHERE bh.collection_time = blocked.collection_time AND bh.snapshot_id = blocked.snapshot_id
                AND bh.blocking_session_id = @head_blocker_session_id
            GROUP BY resource_description
            ORDER BY SUM (wait_duration_ms) DESC
        ) AS primary_wait_resource_description, 
        ISNULL (blocker.command, 'AWAITING COMMAND') AS command, blocker.request_status, 
        wt.category_name, blocker.wait_type, blocker.wait_duration_ms, blocker.wait_resource, blocker.resource_description, 
        blocker.[program_name], blocker.[host_name], blocker.login_name, 
        CONVERT (datetime, SWITCHOFFSET (CAST (blocker.login_time AS datetimeoffset(7)), '+00:00')) AS login_time, 
        blocker.database_name, 
        MAX (blocker.open_transaction_count) AS open_transaction_count, MAX (blocker.transaction_isolation_level) AS transaction_isolation_level, 
        MAX (blocker.request_cpu_time) AS request_cpu_time, MAX (blocker.request_total_elapsed_time) AS request_total_elapsed_time, 
        MIN (blocker.request_start_time) AS request_start_time, MAX (blocker.memory_usage) AS memory_usage, 
        MAX (blocker.session_cpu_time) AS session_cpu_time, MAX (blocker.session_total_scheduled_time) AS session_total_scheduled_time, 
        MAX (blocker.session_row_count) AS session_row_count, MAX (blocker.pending_io_count) AS pending_io_count, 
        MAX (blocker.prev_error) AS prev_error, 
        CONVERT (datetime, SWITCHOFFSET (CAST (MAX (blocker.session_last_request_start_time) AS datetimeoffset(7)), '+00:00')) AS session_last_request_start_time, 
        CONVERT (datetime, SWITCHOFFSET (CAST (MAX (blocker.session_last_request_end_time) AS datetimeoffset(7)), '+00:00')) AS session_last_request_end_time, 
        MAX (blocker.open_resultsets) AS open_resultsets, 
        blocker.plan_handle, blocker.sql_handle, blocker.statement_start_offset, blocker.statement_end_offset, 
        -- RS can't handle binary values as parameters -- convert plan and sql handles to string types
        master.dbo.fn_varbintohexstr (blocker.plan_handle) AS plan_handle_str, 
        master.dbo.fn_varbintohexstr (blocker.sql_handle) AS sql_handle_str, 
        sql_text.[object_id], sql_text.[object_name], sql_text.query_text, 
        REPLACE (REPLACE (REPLACE (REPLACE (REPLACE (REPLACE (
            LEFT (LTRIM (sql_text.query_text), 100), 
            CHAR(9), ' '), CHAR(10), ' '), CHAR(13), ' '), '   ', ' '), '  ', ' '), '  ', ' ') AS flat_query_text
    -- RS won't plot series that only have a single data point on a line graph.  Duplicate each data 
    -- point with a slight time shift, which will allow the chart to provide some visualization even if 
    -- the blocking was transient (only seen during a single sample). For performance reasons, this 
    -- same resultset feeds both a chart and a table.  The duplicate records are flagged with 
    -- chart_only=1 so that they can be filtered out in the table. 
    FROM (
        SELECT *, collection_time AS chart_collection_time, 0 AS chart_only
        FROM #blocking_hierarchy 
        WHERE blocking_session_id != 0 
        UNION ALL 
        SELECT *, DATEADD (second, 10, collection_time) AS chart_collection_time, 1 AS chart_only 
        FROM #blocking_hierarchy 
        WHERE blocking_session_id != 0 
    ) AS blocked
    INNER JOIN (
        SELECT *, collection_time AS chart_collection_time, 0 AS chart_only
        FROM #blocking_hierarchy 
        WHERE blocking_session_id = 0 
        UNION ALL 
        SELECT *, DATEADD (second, 10, collection_time) AS chart_collection_time, 1 AS chart_only
        FROM #blocking_hierarchy 
        WHERE blocking_session_id = 0 
    ) AS blocker
        ON blocked.collection_time = blocker.collection_time AND blocked.snapshot_id = blocker.snapshot_id
            AND blocked.chart_collection_time = blocker.chart_collection_time 
    OUTER APPLY [snapshots].[fn_get_query_text] (blocker.source_id, blocker.sql_handle, blocker.statement_start_offset, blocker.statement_end_offset) AS sql_text
    LEFT OUTER JOIN core.wait_types_categorized AS wt ON blocker.wait_type = wt.wait_type
    WHERE 
        blocker.blocking_session_id = 0 AND blocked.blocking_session_id != 0
    GROUP BY blocked.chart_collection_time, blocked.chart_only, blocked.collection_time, blocked.snapshot_id, 
        blocker.command, blocker.request_status, 
        wt.category_name, blocker.wait_type, blocker.wait_duration_ms, blocker.wait_resource, blocker.resource_description, 
        blocker.[program_name], blocker.[host_name], blocker.login_name, blocker.login_time, blocker.database_name, 
        blocker.plan_handle, blocker.sql_handle, blocker.statement_start_offset, blocker.statement_end_offset, 
        sql_text.[object_id], sql_text.[object_name], sql_text.query_text  
    ORDER BY blocked.collection_time ASC

END 
GO

--
-- snapshots.rpt_active_sessions_and_requests
--  Returns all the active sessions/requests at a particular moment in time
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @collection_time - a time that corresponds to a specific [collection_time] in snapshots.active_sessions_and_requests (UTC)
--
IF (NOT OBJECT_ID(N'snapshots.rpt_active_sessions_and_requests', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[rpt_active_sessions_and_requests] ...'
    DROP PROCEDURE [snapshots].[rpt_active_sessions_and_requests]
END
GO 

PRINT 'Creating procedure [snapshots].[rpt_active_sessions_and_requests] ...'
GO
CREATE PROCEDURE [snapshots].[rpt_active_sessions_and_requests]
    @instance_name sysname, 
    @collection_time datetime
AS
BEGIN
SET NOCOUNT ON;
	-- Find the nearest collection time on or before the user-specified time
	DECLARE @current_collection_time datetimeoffset(7)
	DECLARE @current_snapshot_id int
	DECLARE @query_stats_source_id int

	-- Compensate for RS truncation of fractional seconds 
	SET @current_collection_time = DATEADD(second, 1, @collection_time)

	SELECT TOP 1 @current_collection_time = r.collection_time, @current_snapshot_id = r.snapshot_id
	FROM core.snapshots AS s
	INNER JOIN snapshots.active_sessions_and_requests AS r ON s.snapshot_id = r.snapshot_id
	WHERE s.instance_name = @instance_name
	  AND r.collection_time <= @current_collection_time
	ORDER BY collection_time DESC

	-- Get the source_id for the Query Stats collection set on this server
	SELECT @query_stats_source_id = s.source_id
	FROM core.snapshots AS s
	WHERE s.instance_name = @instance_name AND s.collection_set_uid = '2DC02BD6-E230-4C05-8516-4E8C0EF21F95'

	-- Get all active sessions/requests at that time
	SELECT 
		r.session_id, r.request_id, r.exec_context_id, ISNULL (r.blocking_session_id, 0) AS blocking_session_id, r.blocking_exec_context_id, 
		r.scheduler_id, r.database_name, r.[user_id], r.task_state, r.request_status, r.session_status, 
		r.executing_managed_code, 
		CONVERT (datetime, SWITCHOFFSET (CAST (r.login_time AS datetimeoffset(7)), '+00:00')) AS login_time, 
		r.is_user_process, r.[host_name], r.[program_name], r.login_name, r.wait_type, r.last_wait_type, 
		r.wait_duration_ms, r.wait_resource, r.resource_description, r.transaction_id, 
		r.open_transaction_count, r.transaction_isolation_level, r.request_cpu_time, 
		r.request_logical_reads, r.request_reads, r.request_writes, r.request_total_elapsed_time, 
		CONVERT (datetime, SWITCHOFFSET (CAST (r.request_start_time AS datetimeoffset(7)), '+00:00')) AS request_start_time, 
		r.memory_usage, r.session_cpu_time, r.session_reads, r.session_writes, 
		r.session_logical_reads, r.session_total_scheduled_time, r.session_total_elapsed_time, 
		CONVERT (datetime, SWITCHOFFSET (CAST (r.session_last_request_start_time AS datetimeoffset(7)), '+00:00')) AS session_last_request_start_time, 
		CONVERT (datetime, SWITCHOFFSET (CAST (r.session_last_request_end_time AS datetimeoffset(7)), '+00:00')) AS session_last_request_end_time, 
		r.open_resultsets, r.session_row_count, r.prev_error, r.pending_io_count, ISNULL (r.command, 'AWAITING COMMAND') AS command, 
		r.plan_handle, r.sql_handle, r.statement_start_offset, r.statement_end_offset, 
		CONVERT (datetime, SWITCHOFFSET (CAST (r.collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
		r.snapshot_id, 
		wt.category_name AS wait_category, 
		sql.*, 
		master.dbo.fn_varbintohexstr (r.sql_handle) AS sql_handle_str, 
		master.dbo.fn_varbintohexstr (r.plan_handle) AS plan_handle_str, 
		REPLACE (REPLACE (REPLACE (REPLACE (REPLACE (REPLACE (
			LEFT (LTRIM (sql.query_text), 100)
			, CHAR(9), ' '), CHAR(10), ' '), CHAR(13), ' '), '   ', ' '), '  ', ' '), '  ', ' ') AS flat_query_text 
	FROM snapshots.active_sessions_and_requests AS r
	LEFT OUTER JOIN core.wait_types_categorized AS wt ON r.wait_type = wt.wait_type
	OUTER APPLY snapshots.fn_get_query_text (@query_stats_source_id, r.sql_handle, r.statement_start_offset, r.statement_end_offset) AS sql
	WHERE r.snapshot_id = @current_snapshot_id AND r.collection_time = @current_collection_time
END
GO


--
-- snapshots.rpt_sql_memory_clerks
--  Returns data from os_memory_clerks table
-- Parameters: 
--    @ServerName - SQL Server instance name
--    @EndTime - End of the user-selected time window (UTC)
--    @WindowSize - Number of minutes in the time window 
--
IF (NOT OBJECT_ID(N'snapshots.rpt_sql_memory_clerks', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[rpt_sql_memory_clerks] ...'
    DROP PROCEDURE [snapshots].[rpt_sql_memory_clerks]
END
GO 

PRINT 'Creating procedure [snapshots].[rpt_sql_memory_clerks] ...'
GO
CREATE PROCEDURE [snapshots].[rpt_sql_memory_clerks]
    @ServerName sysname,
    @EndTime datetime = NULL,
    @WindowSize smallint = NULL
AS
BEGIN
    SET NOCOUNT ON;
    
    -- Divide our time window up into 40 evenly-sized time intervals, and find the last collection_time within each of these intervals
    CREATE TABLE #intervals (
        interval_time_id        int, 
        interval_start_time     datetimeoffset(7),
        interval_end_time       datetimeoffset(7),
        interval_id             int, 
        first_collection_time   datetimeoffset(7), 
        last_collection_time    datetimeoffset(7), 
        first_snapshot_id       int,
        last_snapshot_id        int, 
        source_id               int, 
        snapshot_id             int, 
        collection_time         datetimeoffset(7), 
        collection_time_id      int
    )
    -- GUID 49268954-... is the Server Activity CS
    INSERT INTO #intervals
    EXEC [snapshots].[rpt_interval_collection_times] 
        @ServerName, @EndTime, @WindowSize, 'snapshots.os_memory_clerks', '49268954-4FD4-4EB6-AA04-CD59D9BB5714', 40, 0
    
    -- Get memory clerk stats for these collection times
    SELECT 
	    coll.interval_time_id, 
	    -- Convert datetimeoffsets to UTC datetimes
	    CONVERT (datetime, SWITCHOFFSET (coll.interval_start_time, '+00:00')) AS interval_start_time, 
	    CONVERT (datetime, SWITCHOFFSET (coll.interval_end_time,  '+00:00')) AS interval_end_time, 
	    coll.interval_id, 
	    CONVERT (datetime, SWITCHOFFSET (coll.first_collection_time, '+00:00')) AS first_collection_time, 
	    CONVERT (datetime, SWITCHOFFSET (coll.last_collection_time, '+00:00')) AS last_collection_time, 
	    coll.first_snapshot_id, coll.last_snapshot_id, 
	    mc.[type], mc.memory_node_id, mc.single_pages_kb, mc.multi_pages_kb, mc.virtual_memory_reserved_kb, 
        mc.virtual_memory_committed_kb, mc.awe_allocated_kb, mc.shared_memory_reserved_kb, mc.shared_memory_committed_kb, 
        CONVERT (datetime, SWITCHOFFSET (mc.collection_time, '+00:00')) AS collection_time, 
        CAST (mc.single_pages_kb AS bigint) 
            + mc.multi_pages_kb 
            + (CASE WHEN type <> 'MEMORYCLERK_SQLBUFFERPOOL' THEN mc.virtual_memory_committed_kb ELSE 0 END) 
            + mc.shared_memory_committed_kb AS total_kb
    INTO #memory_clerks
    FROM snapshots.os_memory_clerks AS mc
    INNER JOIN #intervals AS coll ON coll.last_snapshot_id = mc.snapshot_id AND coll.last_collection_time = mc.collection_time 

    -- Return memory stats to the caller
    SELECT 
        mc.*, 
        mc.single_pages_kb + mc.multi_pages_kb as allocated_kb,
        ta.total_kb_all_clerks, 
        mc.total_kb / CONVERT(decimal, ta.total_kb_all_clerks) AS percent_total_kb,
        -- There are many memory clerks. We'll chart any that make up 5% of SQL memory or more; less significant clerks will be lumped into an "Other" bucket
        CASE
            WHEN mc.total_kb / CONVERT(decimal, ta.total_kb_all_clerks) > 0.05 THEN mc.[type]
            ELSE N'Other'
        END AS graph_type
    FROM #memory_clerks AS mc
    -- Use a self-join to calculate the total memory allocated for each time interval
    JOIN 
    (
        SELECT 
            mc_ta.collection_time, 
            SUM (mc_ta.total_kb) AS total_kb_all_clerks
        FROM #memory_clerks AS mc_ta
        GROUP BY mc_ta.collection_time
    ) AS ta ON (mc.collection_time = ta.collection_time)
    ORDER BY collection_time
END;
GO


--
-- snapshots.rpt_sql_process_and_system_memory
--  Returns system and SQL process memory details over a time interval
-- Parameters: 
--    @ServerName - SQL Server instance name
--    @EndTime - End of the user-selected time window (UTC)
--    @WindowSize - Number of minutes in the time window 
--
IF (NOT OBJECT_ID(N'snapshots.rpt_sql_process_and_system_memory', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[rpt_sql_process_and_system_memory] ...'
    DROP PROCEDURE [snapshots].[rpt_sql_process_and_system_memory]
END
GO 

PRINT 'Creating procedure [snapshots].[rpt_sql_process_and_system_memory] ...'
GO
CREATE PROCEDURE [snapshots].[rpt_sql_process_and_system_memory]
    @ServerName sysname,
    @EndTime datetime = NULL,
    @WindowSize int
AS
BEGIN
    SET NOCOUNT ON;

    -- Divide our time window up into 40 evenly-sized time intervals, and find the last collection_time within each of these intervals
    CREATE TABLE #intervals (
        interval_time_id        int, 
        interval_start_time     datetimeoffset(7),
        interval_end_time       datetimeoffset(7),
        interval_id             int, 
        first_collection_time   datetimeoffset(7), 
        last_collection_time    datetimeoffset(7), 
        first_snapshot_id       int,
        last_snapshot_id        int, 
        source_id               int,
        snapshot_id             int, 
        collection_time         datetimeoffset(7), 
        collection_time_id      int
    )
    -- GUID 49268954-... is Server Activity
    INSERT INTO #intervals
    EXEC [snapshots].[rpt_interval_collection_times] 
        @ServerName, @EndTime, @WindowSize, 'snapshots.sql_process_and_system_memory', '49268954-4FD4-4EB6-AA04-CD59D9BB5714', 40, 0

    -- Get the earliest and latest snapshot_id values that contain data for the selected time interval. 
    -- This will allow a more efficient query plan. 
    DECLARE @start_snapshot_id int;
    DECLARE @end_snapshot_id int;
    SELECT @start_snapshot_id = MIN (first_snapshot_id)
    FROM #intervals
    SELECT @end_snapshot_id = MAX (last_snapshot_id)
    FROM #intervals
    
    -- Get sys.dm_os_process_memory for these intervals
	SELECT 
		coll.interval_time_id, coll.interval_id, 
		CONVERT (datetime, SWITCHOFFSET (CAST (coll.last_collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
		CONVERT (datetime, SWITCHOFFSET (CAST (coll.interval_start_time AS datetimeoffset(7)), '+00:00')) AS interval_start_time, 
		CONVERT (datetime, SWITCHOFFSET (CAST (coll.interval_end_time AS datetimeoffset(7)), '+00:00')) AS interval_end_time, 
		coll.last_snapshot_id, 
        AVG (sql_physical_memory_in_use_kb)             AS avg_sql_physical_memory_in_use_kb, 
        MAX (sql_physical_memory_in_use_kb)             AS max_sql_physical_memory_in_use_kb, 
        MIN (sql_physical_memory_in_use_kb)             AS min_sql_physical_memory_in_use_kb, 
        AVG (sql_total_virtual_address_space_kb)        AS avg_sql_total_virtual_address_space_kb, 
        AVG (sql_virtual_address_space_reserved_kb)     AS avg_sql_virtual_address_space_reserved_kb, 
        AVG (sql_virtual_address_space_committed_kb)    AS avg_sql_virtual_address_space_committed_kb, 
        AVG (sql_virtual_address_space_available_kb)    AS avg_sql_virtual_address_space_available_kb, 
        MAX (sql_virtual_address_space_available_kb)    AS max_sql_virtual_address_space_available_kb, 
        MIN (sql_virtual_address_space_available_kb)    AS min_sql_virtual_address_space_available_kb, 
        AVG (sql_memory_utilization_percentage)         AS avg_sql_memory_utilization_percentage, 
        MIN (sql_memory_utilization_percentage)         AS min_sql_memory_utilization_percentage, 
        AVG (sql_available_commit_limit_kb)             AS avg_sql_available_commit_limit_kb, 
        MIN (sql_available_commit_limit_kb)             AS min_sql_available_commit_limit_kb, 
        AVG (sql_large_page_allocations_kb)             AS avg_sql_large_page_allocations_kb, 
        AVG (sql_locked_page_allocations_kb)            AS avg_sql_locked_page_allocations_kb, 
        SUM (CAST (sql_process_physical_memory_low AS int)) AS sql_process_physical_memory_low_count, 
        SUM (CAST (sql_process_virtual_memory_low AS int))  AS sql_process_virtual_memory_low_count, 
        MAX (sql_page_fault_count) - MIN (sql_page_fault_count) AS interval_sql_page_fault_count, 
        AVG (system_total_physical_memory_kb)           AS system_total_physical_memory_kb, 
        AVG (system_available_physical_memory_kb)       AS avg_system_available_physical_memory_kb, 
        MAX (system_available_physical_memory_kb)       AS max_system_available_physical_memory_kb, 
        MIN (system_available_physical_memory_kb)       AS min_system_available_physical_memory_kb, 
        AVG (system_total_page_file_kb)                 AS avg_system_total_page_file_kb, 
        AVG (system_available_page_file_kb)             AS avg_system_available_page_file_kb, 
        MIN (system_available_page_file_kb)             AS min_system_available_page_file_kb, 
        AVG (system_cache_kb)                           AS avg_system_cache_kb, 
        AVG (system_kernel_paged_pool_kb)               AS avg_system_kernel_paged_pool_kb, 
        AVG (system_kernel_nonpaged_pool_kb)            AS avg_system_kernel_nonpaged_pool_kb, 
        SUM (CAST (system_high_memory_signal_state AS int)) AS system_high_memory_signal_state_count, 
        SUM (CAST (system_low_memory_signal_state AS int))  AS system_low_memory_signal_state_count, 
        AVG (bpool_commit_target)                       AS avg_bpool_commit_target, 
        MAX (bpool_commit_target)                       AS max_bpool_commit_target,  
        MIN (bpool_commit_target)                       AS min_bpool_commit_target, 
        AVG (bpool_committed)                           AS avg_bpool_committed, 
        MAX (bpool_committed)                           AS max_bpool_committed,  
        MIN (bpool_committed)                           AS min_bpool_committed, 
        AVG (bpool_visible)                             AS avg_bpool_visible, 
        MAX (bpool_visible)                             AS max_bpool_visible,  
        MIN (bpool_visible)                             AS min_bpool_visible 
    FROM snapshots.sql_process_and_system_memory AS pm
    INNER JOIN #intervals AS coll ON coll.last_snapshot_id = pm.snapshot_id AND coll.last_collection_time = pm.collection_time     
    GROUP BY 
        coll.interval_start_time, coll.interval_end_time, coll.interval_time_id, coll.last_collection_time, coll.interval_id, coll.last_snapshot_id
    ORDER BY coll.last_collection_time ASC;
END
GO


--
-- snapshots.rpt_sampled_waits
--  Returns a list of the apps and queries that spent the most time waiting for the specified wait category. 
--  Note that this is based off data collected from regular samples of sys.dm_exec_requests, 
--  sys.dm_os_waiting_tasks, and related DMVs.  Because these are just samples, it is not expected that 
--  all waits will be captured; at best, a representative sampling will be returned.  
-- 
-- Parameters: 
--    @ServerName - SQL Server instance name
--    @EndTime - end of the time window (UTC)
--    @WindowSize - number of minutes in the time window
--    @CategoryName - Optional filter criteria: Name of wait category
--    @WaitType - Optional filter criteria: Name of wait type
--    @ProgramName - Optional filter criteria: Application name
--    @SqlHandleStr - Optional filter criteria: Handle to a particular query
--    @StatementStartOffset - Start offset for a particular statement within the batch/proc specified by @SqlHandleStr
--    @StatementEndOffset - End offset for a particular statement within the batch/proc specified by @SqlHandleStr
--    @SessionID - Optional filter criteria: Specific SPID
--    @Database - Optional filter criteria: Waits within a particular database
--
IF (NOT OBJECT_ID(N'snapshots.rpt_sampled_waits', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[rpt_sampled_waits] ...'
    DROP PROCEDURE [snapshots].[rpt_sampled_waits]
END
GO 

PRINT 'Creating procedure [snapshots].[rpt_sampled_waits] ...'
GO
CREATE PROCEDURE [snapshots].[rpt_sampled_waits]
    @ServerName sysname, 
    @EndTime datetime, 
    @WindowSize int, 
    @CategoryName nvarchar(20) = NULL, 
    @WaitType nvarchar(45) = NULL, 
    @ProgramName nvarchar(50) = NULL, 
    @SqlHandleStr varchar(130) = NULL, 
    @StatementStartOffset int = NULL, 
    @StatementEndOffset int = NULL, 
    @SessionID int = NULL, 
    @Database nvarchar(255) = NULL
AS
BEGIN
    SET NOCOUNT ON;
    DECLARE @start_time_internal datetimeoffset(7);
    DECLARE @end_time_internal datetimeoffset(7);

    -- Clean string params (on drillthrough, RS may pass in an empty string instead of NULL)
    IF @CategoryName = '' SET @CategoryName = NULL
    IF @WaitType = '' SET @WaitType = NULL
    IF @ProgramName = '' SET @ProgramName = NULL
    IF @SqlHandleStr = '' SET @SqlHandleStr = NULL
    IF @Database = '' SET @Database = NULL
    -- -1 is a potentially valid offset, but anything less is invalid. RS can't represent NULL in some places, so 
    -- translate int values that are out of range to NULL. 
    IF @StatementStartOffset < -1 SET @StatementStartOffset = NULL
    IF @StatementEndOffset < -1 SET @StatementStartOffset = NULL
    IF @SessionID < -1 SET @SessionID = NULL

    -- NOTE: The logic below is duplicated in snapshots.rpt_sampled_waits_longest.  It cannot be moved to a shared 
    -- child proc because of SQL restrictions (no nested INSERT EXECs, and table params are read only).  
    -- Also update snapshots.rpt_sampled_waits_longest if a change to this section is required. 

    /*** BEGIN DUPLICATED CODE SECTION ***/
        -- Start time should be passed in as a UTC datetime
        IF (@EndTime IS NOT NULL)
        BEGIN
            -- Assumed time zone offset for this conversion is +00:00 (datetime must be passed in as UTC)
            SET @end_time_internal = CAST (@EndTime AS datetimeoffset(7));
        END
        ELSE BEGIN
            SELECT @end_time_internal = MAX(ar.collection_time)
            FROM core.snapshots AS s
            INNER JOIN snapshots.active_sessions_and_requests AS ar ON s.snapshot_id = ar.snapshot_id
            WHERE s.instance_name = @ServerName -- AND collection_set_uid = '49268954-4FD4-4EB6-AA04-CD59D9BB5714' -- Server Activity CS
        END
        SET @start_time_internal = DATEADD (minute, -1 * @WindowSize, @end_time_internal);

        DECLARE @sql_handle varbinary(64)
        IF LEN (@SqlHandleStr) > 0
        BEGIN
            SET @sql_handle = snapshots.fn_hexstrtovarbin (@SqlHandleStr)
        END
        
        -- Divide our time window up into 40 evenly-sized time intervals, and find the first and last collection_time within each of these intervals
        CREATE TABLE #intervals (
            interval_time_id        int, 
            interval_start_time     datetimeoffset(7),
            interval_end_time       datetimeoffset(7),
            interval_id             int, 
            first_collection_time   datetimeoffset(7), 
            last_collection_time    datetimeoffset(7), 
            first_snapshot_id       int,
            last_snapshot_id        int, 
            source_id               int, 
            snapshot_id             int, 
            collection_time         datetimeoffset(7), 
            collection_time_id      int
        )
        INSERT INTO #intervals
        EXEC [snapshots].[rpt_interval_collection_times] 
            @ServerName, @EndTime, @WindowSize, 'snapshots.active_sessions_and_requests', '2dc02bd6-e230-4c05-8516-4e8c0ef21f95', 40, 1

        SELECT 
            ti.interval_id, ti.interval_time_id, ti.interval_start_time, ti.interval_end_time, 
            ti.collection_time, ti.collection_time_id, r.row_id, 
            r.snapshot_id, r.session_id, r.request_id, r.exec_context_id, r.wait_duration_ms, r.wait_resource, 
            r.login_time, r.program_name, r.sql_handle, r.statement_start_offset, r.statement_end_offset, r.plan_handle, 
            r.database_name, r.task_state, ti.source_id, wt.ignore, 
            -- Model CPU as a "wait type" in the sampling results.  Any active request without a wait type is assumed to be CPU-bound. 
            CASE -- same expression here as in the GROUP BY
                WHEN r.wait_type = '' AND r.task_state IS NOT NULL THEN 'CPU'
                ELSE wt.category_name 
            END AS category_name, 
            -- "Running" tasks are actively using the CPU.  A "runnable" task is able to run, but is momentarily waiting for the active 
            -- task to yield so the next runnable task can get scheduled. Map runnable to the SOS_SCHEDULER_YIELD wait type.
            CASE 
                WHEN r.wait_type = '' AND r.task_state = 'RUNNING' THEN 'CPU (Consumed)'
                WHEN r.wait_type = '' AND r.task_state != 'RUNNING' THEN 'SOS_SCHEDULER_YIELD'
                ELSE r.wait_type
            END AS wait_type
        INTO #waiting_tasks
        FROM snapshots.active_sessions_and_requests AS r
        LEFT OUTER JOIN core.wait_types_categorized AS wt ON wt.wait_type = r.wait_type 
        INNER JOIN #intervals AS ti ON r.collection_time = ti.collection_time AND r.snapshot_id = ti.snapshot_id 
        WHERE r.collection_time BETWEEN @start_time_internal AND @end_time_internal 
            AND r.command != 'AWAITING COMMAND' AND r.request_id != -1 -- exclude idle spids (e.g. head blockers)
            AND (r.program_name = @ProgramName OR @ProgramName IS NULL)
            AND (r.sql_handle = @sql_handle OR @SqlHandleStr IS NULL)
            AND (r.database_name = @Database OR @Database IS NULL)
            AND (r.statement_start_offset = @StatementStartOffset OR @SqlHandleStr IS NULL)
            AND (r.statement_end_offset = @StatementEndOffset OR @SqlHandleStr IS NULL)
            AND (r.session_id = @SessionID OR @SessionID IS NULL)
            AND 
            ( -- ... and wait category either matches the user-specified parameter ...
                (@CategoryName = 
                    CASE -- same expression here as in the select column list
                        WHEN r.wait_type = '' AND r.task_state IS NOT NULL THEN 'CPU'
                        ELSE wt.category_name 
                    END
                ) 
                -- ... or a filter parameter for wait category was not provided (return all categories that aren't marked as ignorable). 
                OR (@CategoryName IS NULL AND ISNULL (wt.ignore, 0) = 0)
            )
            AND 
            ( -- ... and wait type either matches the user-specified parameter ...
                (@WaitType = 
                    CASE 
                        WHEN r.wait_type = '' AND r.task_state = 'RUNNING' THEN 'CPU (Consumed)'
                        WHEN r.wait_type = '' AND r.task_state != 'RUNNING' THEN 'SOS_SCHEDULER_YIELD'
                        ELSE r.wait_type
                    END 
                )
                -- ... or a filter parameter for wait category was not provided 
                OR @WaitType IS NULL
            )            
        -- Force a recompile of this statement to take into account the actual values of @start_time_internal and 
        -- @end_time_internal, which were not available at the time of proc compilation. 
        OPTION (RECOMPILE);
        
    /*** END DUPLICATED CODE SECTION ***/
    
    -- Get query text (do this here instead of in the following query so that we don't waste time retrieving the 
    -- same query's text more than once). 
    SELECT 
        r.sql_handle, r.statement_start_offset, r.statement_end_offset, 
        REPLACE (REPLACE (REPLACE (REPLACE (REPLACE (REPLACE (
            LEFT (LTRIM (qt.query_text), 100)
            , CHAR(9), ' '), CHAR(10), ' '), CHAR(13), ' '), '   ', ' '), '  ', ' '), '  ', ' ') AS flat_query_text
    INTO #queries
    FROM 
    (
        SELECT DISTINCT source_id, sql_handle, statement_start_offset, statement_end_offset
        FROM #waiting_tasks
        WHERE category_name IS NOT NULL 
    ) AS r
    OUTER APPLY snapshots.fn_get_query_text(r.source_id, r.sql_handle, r.statement_start_offset, r.statement_end_offset) AS qt

    -- Within each time interval, group the wait counts by waittype, app, db, and query. 
    SELECT 
        CONVERT (datetime, SWITCHOFFSET (r.interval_start_time, '+00:00')) AS interval_start_time, 
        CONVERT (datetime, SWITCHOFFSET (r.interval_end_time, '+00:00')) AS interval_end_time, 
        r.interval_id, r.interval_time_id, 
        r.source_id, r.category_name, r.wait_type, r.[program_name], r.database_name, 
        COUNT (*) AS wait_count, r.sql_handle, r.statement_start_offset, r.statement_end_offset,
        master.dbo.fn_varbintohexstr (r.sql_handle) AS sql_handle_str, 
        q.flat_query_text
    FROM #waiting_tasks AS r 
    LEFT OUTER JOIN #queries AS q ON r.sql_handle = q.sql_handle AND r.statement_start_offset = q.statement_start_offset 
        AND r.statement_end_offset = q.statement_end_offset 
    WHERE r.category_name IS NOT NULL 
    GROUP BY r.interval_start_time, r.interval_end_time, r.interval_id, r.interval_time_id,
        r.category_name, r.wait_type, r.[program_name], r.database_name, r.[sql_handle], 
        r.source_id, r.statement_start_offset, r.statement_end_offset, q.flat_query_text
    ORDER BY r.category_name, r.interval_id, COUNT(*) DESC

END 
GO


--
-- snapshots.rpt_sampled_waits_longest
--  Returns list of the N longest waits that meet user-specified filter criteria.  
--  Note that this is based off data collected from regular samples of sys.dm_exec_requests, 
--  sys.dm_os_waiting_tasks, and related DMVs.  Because these are just samples, it is not expected that 
--  all waits will be captured; at best, a representative sampling will be returned.  
-- 
-- Parameters: 
--    @ServerName - SQL Server instance name
--    @EndTime - end of the time window (UTC)
--    @WindowSize - number of minutes in the time window
--    @CategoryName - Optional filter criteria: Name of wait category
--    @WaitType - Optional filter criteria: Name of wait type
--    @ProgramName - Optional filter criteria: Application name
--    @SqlHandleStr - Optional filter criteria: Name of the query to filter on
--    @StatementStartOffset - Start offset for a particular statement within the batch/proc specified by @SqlHandleStr
--    @StatementEndOffset - End offset for a particular statement within the batch/proc specified by @SqlHandleStr
--    @SessionID - Optional filter criteria: Specific SPID
--    @Database - Optional filter criteria: Waits within a particular database
--
IF (NOT OBJECT_ID(N'snapshots.rpt_sampled_waits_longest', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[rpt_sampled_waits_longest] ...'
    DROP PROCEDURE [snapshots].[rpt_sampled_waits_longest]
END
GO 

PRINT 'Creating procedure [snapshots].[rpt_sampled_waits_longest] ...'
GO
CREATE PROCEDURE [snapshots].[rpt_sampled_waits_longest]
    @ServerName sysname, 
    @EndTime datetime, 
    @WindowSize int, 
    @CategoryName nvarchar(20) = NULL, 
    @WaitType nvarchar(45) = NULL, 
    @ProgramName nvarchar(50) = NULL, 
    @SqlHandleStr varchar(130) = NULL, 
    @StatementStartOffset int = NULL, 
    @StatementEndOffset int = NULL, 
    @SessionID int = NULL, 
    @Database nvarchar(255) = NULL
AS
BEGIN
    SET NOCOUNT ON;
    DECLARE @start_time_internal datetimeoffset(7);
    DECLARE @end_time_internal datetimeoffset(7);

    -- Clean string params (on drillthrough, RS may pass in an empty string instead of NULL)
    IF @CategoryName = '' SET @CategoryName = NULL
    IF @WaitType = '' SET @WaitType = NULL
    IF @ProgramName = '' SET @ProgramName = NULL
    IF @SqlHandleStr = '' SET @SqlHandleStr = NULL
    IF @Database = '' SET @Database = NULL
    -- -1 is a potentially valid offset, but anything less is invalid. RS can't represent NULL in some places, so 
    -- translate int values that are out of range to NULL. 
    IF @StatementStartOffset < -1 SET @StatementStartOffset = NULL
    IF @StatementEndOffset < -1 SET @StatementStartOffset = NULL
    IF @SessionID < -1 SET @SessionID = NULL

    -- NOTE: The logic below is duplicated in snapshots.rpt_sampled_waits.  It cannot be moved to a shared 
    -- child proc because of SQL restrictions (no nested INSERT EXECs, and table params are read only).  
    -- Also update snapshots.rpt_sampled_waits if a change to this section is required. 

    /*** BEGIN DUPLICATED CODE SECTION ***/
        -- Start time should be passed in as a UTC datetime
        IF (@EndTime IS NOT NULL)
        BEGIN
            -- Assumed time zone offset for this conversion is +00:00 (datetime must be passed in as UTC)
            SET @end_time_internal = CAST (@EndTime AS datetimeoffset(7));
        END
        ELSE BEGIN
            SELECT @end_time_internal = MAX(ar.collection_time)
            FROM core.snapshots AS s
            INNER JOIN snapshots.active_sessions_and_requests AS ar ON s.snapshot_id = ar.snapshot_id
            WHERE s.instance_name = @ServerName -- AND collection_set_uid = '49268954-4FD4-4EB6-AA04-CD59D9BB5714' -- Server Activity CS
        END
        SET @start_time_internal = DATEADD (minute, -1 * @WindowSize, @end_time_internal);

        DECLARE @sql_handle varbinary(64)
        IF LEN (@SqlHandleStr) > 0
        BEGIN
            SET @sql_handle = snapshots.fn_hexstrtovarbin (@SqlHandleStr)
        END
        
        -- Divide our time window up into 40 evenly-sized time intervals, and find the first and last collection_time within each of these intervals
        CREATE TABLE #intervals (
            interval_time_id        int, 
            interval_start_time     datetimeoffset(7),
            interval_end_time       datetimeoffset(7),
            interval_id             int, 
            first_collection_time   datetimeoffset(7), 
            last_collection_time    datetimeoffset(7), 
            first_snapshot_id       int,
            last_snapshot_id        int, 
            source_id               int, 
            snapshot_id             int, 
            collection_time         datetimeoffset(7), 
            collection_time_id      int
        )
        INSERT INTO #intervals
        EXEC [snapshots].[rpt_interval_collection_times] 
            @ServerName, @EndTime, @WindowSize, 'snapshots.active_sessions_and_requests', '2dc02bd6-e230-4c05-8516-4e8c0ef21f95', 40, 1

        SELECT 
            ti.interval_id, ti.interval_time_id, ti.interval_start_time, ti.interval_end_time, 
            ti.collection_time, ti.collection_time_id, r.row_id, 
            r.snapshot_id, r.session_id, r.request_id, r.exec_context_id, r.wait_duration_ms, r.wait_resource, 
            r.login_time, r.program_name, r.sql_handle, r.statement_start_offset, r.statement_end_offset, r.plan_handle, 
            r.database_name, r.task_state, ti.source_id, wt.ignore, 
            -- Model CPU as a "wait type" in the sampling results.  Any active request without a wait type is assumed to be CPU-bound. 
            CASE -- same expression here as in the GROUP BY
                WHEN r.wait_type = '' AND r.task_state IS NOT NULL THEN 'CPU'
                ELSE wt.category_name 
            END AS category_name, 
            -- "Running" tasks are actively using the CPU.  A "runnable" task is able to run, but is momentarily waiting for the active 
            -- task to yield so the next runnable task can get scheduled. Map runnable to the SOS_SCHEDULER_YIELD wait type.
            CASE 
                WHEN r.wait_type = '' AND r.task_state = 'RUNNING' THEN 'CPU (Consumed)'
                WHEN r.wait_type = '' AND r.task_state != 'RUNNING' THEN 'SOS_SCHEDULER_YIELD'
                ELSE r.wait_type
            END AS wait_type
        INTO #waiting_tasks
        FROM snapshots.active_sessions_and_requests AS r
        LEFT OUTER JOIN core.wait_types_categorized AS wt ON wt.wait_type = r.wait_type 
        INNER JOIN #intervals AS ti ON r.collection_time = ti.collection_time AND r.snapshot_id = ti.snapshot_id 
        WHERE r.collection_time BETWEEN @start_time_internal AND @end_time_internal 
            AND r.command != 'AWAITING COMMAND' AND r.request_id != -1 -- exclude idle spids (e.g. head blockers)
            AND (r.program_name = @ProgramName OR @ProgramName IS NULL)
            AND (r.sql_handle = @sql_handle OR @SqlHandleStr IS NULL)
            AND (r.database_name = @Database OR @Database IS NULL)
            AND (r.statement_start_offset = @StatementStartOffset OR @SqlHandleStr IS NULL)
            AND (r.statement_end_offset = @StatementEndOffset OR @SqlHandleStr IS NULL)
            AND (r.session_id = @SessionID OR @SessionID IS NULL)
            AND 
            ( -- ... and wait category either matches the user-specified parameter ...
                (@CategoryName = 
                    CASE -- same expression here as in the select column list
                        WHEN r.wait_type = '' AND r.task_state IS NOT NULL THEN 'CPU'
                        ELSE wt.category_name 
                    END
                ) 
                -- ... or a filter parameter for wait category was not provided (return all categories that aren't marked as ignorable). 
                OR (@CategoryName IS NULL AND ISNULL (wt.ignore, 0) = 0)
            )
            AND 
            ( -- ... and wait type either matches the user-specified parameter ...
                (@WaitType = 
                    CASE 
                        WHEN r.wait_type = '' AND r.task_state = 'RUNNING' THEN 'CPU (Consumed)'
                        WHEN r.wait_type = '' AND r.task_state != 'RUNNING' THEN 'SOS_SCHEDULER_YIELD'
                        ELSE r.wait_type
                    END 
                )
                -- ... or a filter parameter for wait category was not provided 
                OR @WaitType IS NULL
            )
        -- Force a recompile of this statement to take into account the actual values of @start_time_internal and 
        -- @end_time_internal, which were not available at the time of proc compilation. 
        OPTION (RECOMPILE);
        
    /*** END DUPLICATED CODE SECTION ***/

    CREATE INDEX idx1 ON #waiting_tasks (collection_time_id, session_id)

    -- The same long-lived wait may be captured multiple times.  For a given wait, we only care about the max 
    -- wait time within the target time window. If we see that the immediately following sample shows the same 
    -- spid waiting with a wait time that spans both samples, we discard the preceding sample and keep only 
    -- the max wait time.  For example, in the data below, spid 54 was stuck in the same long-lived lock wait 
    -- from row 1 through row 3.  From this data, we would only keep rows #3 and #4. Rows #1 and #2 are 
    -- discarded to avoid reporting the same long wait several times. 
    -- 
    --     (row#)   collection_time     session_id  wait_type    wait_duration_ms  
    --              ------------------- ----------- ------------ -----------------
    --       1      2007-10-25 13:01:00          53 LCK_M_S                   8141
    --       2      2007-10-25 13:01:10          53 LCK_M_S                  18278
    --       3      2007-10-25 13:01:20          53 LCK_M_S                  28318
    --       4      2007-10-25 13:01:30          54 LCK_M_X                    755
    --
    -- Also, if there was one collection time where everyone was blocked momentarily, we want to avoid 
    -- reporting that collection time over and over w/only the spid # varying; that's not the most interesting 
    -- sampling.  Instead, report the longest wait in each of the top 10 collection times (top 10 times by max 
    -- wait duration at the collection time).  
    SELECT TOP 10
        CONVERT (datetime, SWITCHOFFSET (r1.collection_time, '+00:00')) AS collection_time, 
        CONVERT (varchar(30), CONVERT (datetime, SWITCHOFFSET (r1.collection_time, '+00:00')), 126) AS collection_time_str, 
        r1.snapshot_id, r1.row_id, 
        r1.session_id, r1.request_id, r1.exec_context_id, r1.wait_resource, 
        r1.source_id, r1.category_name, r1.wait_type, r1.wait_duration_ms, 
        r1.database_name, r1.[program_name], r1.[sql_handle], r1.statement_start_offset, r1.statement_end_offset, r1.plan_handle
    FROM #waiting_tasks AS r1
    -- Find the same spid in the next collection time
    LEFT OUTER JOIN #waiting_tasks AS r2 
        ON r2.collection_time_id = r1.collection_time_id + 1 AND r2.session_id = r1.session_id 
            AND r2.request_id = r1.request_id AND r2.exec_context_id = r1.exec_context_id AND r2.login_time = r1.login_time 
    WHERE 
        -- Prevent reporting the same wait spanning multiple collection times. 
        (   
            -- ... where the spid wasn't seen waiting at the next collection time
            r2.session_id IS NULL 
            -- ... or the wait at the next collection time is shorter than it would have been if the wait had spanned both collection times 
            OR r2.wait_duration_ms < (DATEDIFF (second, r1.collection_time, r2.collection_time) * 1000)
        )
        -- Exclude all but the longest wait in any given collection time
        AND NOT EXISTS 
        (
            SELECT * FROM #waiting_tasks AS r3
            WHERE r3.collection_time_id = r1.collection_time_id 
                AND r3.wait_duration_ms > r1.wait_duration_ms 
                OR (r3.wait_duration_ms = r1.wait_duration_ms AND r3.row_id > r1.row_id)
        )
    ORDER BY r1.wait_duration_ms DESC
   
END
GO


--
-- snapshots.rpt_sampled_waits_hottest_resources
--  Returns list of the N longest waits that meet user-specified filter criteria.  
--  Note that this is based off data collected from regular samples of sys.dm_exec_requests, 
--  sys.dm_os_waiting_tasks, and related DMVs.  Because these are just samples, it is not expected that 
--  all waits will be captured; at best, a representative sampling will be returned.  
-- 
-- Parameters: 
--    @ServerName - SQL Server instance name
--    @EndTime - end of the time window (UTC)
--    @WindowSize - number of minutes in the time window
--    @CategoryName - Optional filter criteria: Name of wait category
--    @WaitType - Optional filter criteria: Name of wait type
--    @ProgramName - Optional filter criteria: Application name
--    @SqlHandleStr - Optional filter criteria: Name of the query to filter on
--    @StatementStartOffset - Start offset for a particular statement within the batch/proc specified by @SqlHandleStr
--    @StatementEndOffset - End offset for a particular statement within the batch/proc specified by @SqlHandleStr
--    @SessionID - Option filter criteria: Specific SPID
--    @Database - Optional filter criteria: Waits within a particular database
--
IF (NOT OBJECT_ID(N'snapshots.rpt_sampled_waits_hottest_resources', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[rpt_sampled_waits_hottest_resources] ...'
    DROP PROCEDURE [snapshots].[rpt_sampled_waits_hottest_resources]
END
GO 

PRINT 'Creating procedure [snapshots].[rpt_sampled_waits_hottest_resources] ...'
GO
CREATE PROCEDURE [snapshots].[rpt_sampled_waits_hottest_resources]
    @ServerName sysname, 
    @EndTime datetime, 
    @WindowSize int, 
    @CategoryName nvarchar(20) = NULL, 
    @WaitType nvarchar(45) = NULL, 
    @ProgramName nvarchar(50) = NULL, 
    @SqlHandleStr varchar(130) = NULL, 
    @StatementStartOffset int = NULL, 
    @StatementEndOffset int = NULL, 
    @SessionID int = NULL, 
    @Database nvarchar(255) = NULL
AS
BEGIN
    SET NOCOUNT ON;
    DECLARE @start_time_internal datetimeoffset(7);
    DECLARE @end_time_internal datetimeoffset(7);

    -- Clean string params (on drillthrough, RS may pass in an empty string instead of NULL)
    IF @CategoryName = '' SET @CategoryName = NULL
    IF @WaitType = '' SET @WaitType = NULL
    IF @ProgramName = '' SET @ProgramName = NULL
    IF @SqlHandleStr = '' SET @SqlHandleStr = NULL
    IF @Database = '' SET @Database = NULL
    -- -1 is a potentially valid offset, but anything less is invalid. RS can't represent NULL in some places, so 
    -- translate int values that are out of range to NULL. 
    IF @StatementStartOffset < -1 SET @StatementStartOffset = NULL
    IF @StatementEndOffset < -1 SET @StatementStartOffset = NULL
    IF @SessionID < -1 SET @SessionID = NULL

    -- Start time should be passed in as a UTC datetime
    IF (@EndTime IS NOT NULL)
    BEGIN
        -- Assumed time zone offset for this conversion is +00:00 (datetime must be passed in as UTC)
        SET @end_time_internal = CAST (@EndTime AS datetimeoffset(7));
    END
    ELSE BEGIN
        SELECT @end_time_internal = MAX(ar.collection_time)
        FROM core.snapshots AS s
        INNER JOIN snapshots.active_sessions_and_requests AS ar ON s.snapshot_id = ar.snapshot_id
        WHERE s.instance_name = @ServerName -- AND collection_set_uid = '49268954-4FD4-4EB6-AA04-CD59D9BB5714' -- Server Activity CS
    END
    SET @start_time_internal = DATEADD (minute, -1 * @WindowSize, @end_time_internal);

    DECLARE @sql_handle varbinary(64)
    IF LEN (@SqlHandleStr) > 0
    BEGIN
        SET @sql_handle = snapshots.fn_hexstrtovarbin (@SqlHandleStr)
    END
    
    SELECT TOP 10 
        wt.category_name, r.wait_type, 
        CASE 
            WHEN LEN (ISNULL (r.wait_resource, '')) = 0 THEN 
                CASE 
                    WHEN LEN (r.resource_description) > 30 THEN LEFT (r.resource_description, 27) + '...'
                    ELSE LEFT (r.resource_description, 30)
                END
            ELSE r.wait_resource
        END AS wait_resource, 
        r.resource_description, 
        CONVERT (datetime, SWITCHOFFSET (MAX (r.collection_time), '+00:00')) AS example_collection_time, 
        CONVERT (varchar(30), CONVERT (datetime, SWITCHOFFSET (MAX (r.collection_time), '+00:00')), 126) AS example_collection_time_str, 
        COUNT(*) AS wait_count
    FROM snapshots.active_sessions_and_requests AS r 
    LEFT OUTER JOIN core.wait_types_categorized AS wt ON wt.wait_type = r.wait_type 
    WHERE r.collection_time BETWEEN @start_time_internal AND @end_time_internal 
        AND r.command != 'AWAITING COMMAND' AND r.request_id != -1 -- exclude idle spids (e.g. head blockers)
        AND (r.program_name = @ProgramName OR @ProgramName IS NULL)
        AND (r.sql_handle = @sql_handle OR @SqlHandleStr IS NULL)
        AND (r.database_name = @Database OR @Database IS NULL)
        AND (r.statement_start_offset = @StatementStartOffset OR @SqlHandleStr IS NULL)
        AND (r.statement_end_offset = @StatementEndOffset OR @SqlHandleStr IS NULL)
        AND (r.session_id = @SessionID OR @SessionID IS NULL)
        AND 
        ( -- ... and wait category either matches the user-specified parameter ...
            (@CategoryName = 
                CASE -- same expression here as in the select column list
                    WHEN r.wait_type = '' AND r.task_state IS NOT NULL THEN 'CPU'
                    ELSE wt.category_name 
                END
            ) 
            -- ... or a filter parameter for wait category was not provided (return all categories that aren't marked as ignorable). 
            OR (@CategoryName IS NULL AND ISNULL (wt.ignore, 0) = 0)
        )
        AND 
        ( -- ... and wait type either matches the user-specified parameter ...
            (@WaitType = 
                CASE 
                    WHEN r.wait_type = '' AND r.task_state = 'RUNNING' THEN 'CPU (Consumed)'
                    WHEN r.wait_type = '' AND r.task_state != 'RUNNING' THEN 'SOS_SCHEDULER_YIELD'
                    ELSE r.wait_type
                END 
            )
            -- ... or a filter parameter for wait category was not provided 
            OR @WaitType IS NULL
        )
        -- Exclude rows where there is no named resource
        AND (ISNULL (r.resource_description, '') != '' OR ISNULL (r.wait_resource, '') != '')
    GROUP BY wt.category_name, r.wait_type, r.wait_resource, r.resource_description
    ORDER BY COUNT(*) DESC
    -- Force a recompile of this statement to take into account the actual values of @start_time_internal and 
    -- @end_time_internal, which were not available at the time of proc compilation. 
    OPTION (RECOMPILE);
   
END
GO


--
-- snapshots.rpt_io_virtual_file_stats
--  Returns wait time per wait type over a time interval
-- Parameters: 
--    @ServerName - SQL Server instance name
--    @EndTime - End of the user-selected time window (UTC)
--    @WindowSize - Number of minutes in the time window 
--    @CategoryName - (Optional) Name of wait category to filter on (all categories if NULL)
--    @WaitType - (Optional) Name of wait type to filter on (all wait types if NULL)
--
IF (NOT OBJECT_ID(N'snapshots.rpt_io_virtual_file_stats', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[rpt_io_virtual_file_stats] ...'
    DROP PROCEDURE [snapshots].[rpt_io_virtual_file_stats]
END
GO 

PRINT 'Creating procedure [snapshots].[rpt_io_virtual_file_stats] ...'
GO
CREATE PROCEDURE [snapshots].[rpt_io_virtual_file_stats]
    @ServerName sysname,
    @EndTime datetime = NULL,
    @WindowSize int,
    @LogicalDisk nvarchar(255) = NULL, 
    @Database nvarchar(255) = NULL 
AS
BEGIN
    SET NOCOUNT ON;

    -- Clean string params (on drillthrough, RS may pass in empty string instead of NULL)
    IF @LogicalDisk = '' SET @LogicalDisk = NULL
    IF @Database = '' SET @Database = NULL

    -- Divide our time window up into 40 evenly-sized time intervals, and find the last collection_time within each of these intervals
    CREATE TABLE #intervals (
        interval_time_id        int, 
        interval_start_time     datetimeoffset(7),
        interval_end_time       datetimeoffset(7),
        interval_id             int, 
        first_collection_time   datetimeoffset(7), 
        last_collection_time    datetimeoffset(7), 
        first_snapshot_id       int,
        last_snapshot_id        int, 
        source_id               int,
        snapshot_id             int, 
        collection_time         datetimeoffset(7), 
        collection_time_id      int
    )
    -- GUID 49268954-... is Server Activity
    INSERT INTO #intervals
    EXEC [snapshots].[rpt_interval_collection_times] 
        @ServerName, @EndTime, @WindowSize, 'snapshots.io_virtual_file_stats', '49268954-4FD4-4EB6-AA04-CD59D9BB5714', 40, 0

    -- Get the earliest and latest snapshot_id values that contain data for the selected time interval. 
    -- This will allow a more efficient query plan. 
    DECLARE @start_snapshot_id int;
    DECLARE @end_snapshot_id int;
    SELECT @start_snapshot_id = MIN (first_snapshot_id)
    FROM #intervals
    SELECT @end_snapshot_id = MAX (last_snapshot_id)
    FROM #intervals
    
    -- Get the file stats for these collection times
	SELECT 
		coll.interval_id, coll.interval_time_id, coll.interval_start_time, coll.interval_end_time, 
		coll.first_collection_time, coll.last_collection_time, coll.first_snapshot_id, coll.last_snapshot_id, 
		fs.*
    INTO #file_stats
    FROM snapshots.io_virtual_file_stats AS fs
    INNER JOIN #intervals AS coll ON coll.last_snapshot_id = fs.snapshot_id AND coll.last_collection_time = fs.collection_time 
    WHERE 
        fs.logical_disk = ISNULL (@LogicalDisk, fs.logical_disk)
        AND fs.database_name = ISNULL (@Database, fs.database_name)
    
    -- Get file stats deltas for each interval.  
    SELECT 
        t.*, 
        /**** Combined reads + write values ****/
        t.num_of_reads_delta + t.num_of_writes_delta                    AS num_of_transfers_delta, 
        t.num_of_reads_cumulative + t.num_of_writes_cumulative          AS num_of_transfers_cumulative,  
        t.num_of_mb_read_delta + t.num_of_mb_written_delta              AS num_of_mb_transferred_delta, 
        t.num_of_mb_read_cumulative + t.num_of_mb_written_cumulative    AS num_of_mb_transferred_cumulative, 
        /**** Calc "Disk sec/Transfer" type values ***/
        t.io_stall_read_ms_delta + t.io_stall_write_ms_delta            AS io_stall_ms_delta, 
        t.io_stall_read_ms_cumulative + t.io_stall_write_ms_cumulative  AS io_stall_ms_cumulative, 
        CASE 
            WHEN t.num_of_reads_delta = 0 THEN 0 
            ELSE t.io_stall_read_ms_delta / t.num_of_reads_delta
        END                                                             AS io_stall_ms_per_read_delta, 
        CASE 
            WHEN t.num_of_reads_cumulative = 0 THEN 0 
            ELSE t.io_stall_read_ms_cumulative / t.num_of_reads_cumulative
        END                                                             AS io_stall_ms_per_read_cumulative, 
        CASE
            WHEN t.num_of_writes_delta = 0 THEN 0
            ELSE t.io_stall_write_ms_delta / t.num_of_writes_delta
        END                                                             AS io_stall_ms_per_write_delta, 
        CASE
            WHEN t.num_of_writes_cumulative = 0 THEN 0
            ELSE t.io_stall_write_ms_cumulative / t.num_of_writes_cumulative
        END                                                             AS io_stall_ms_per_write_cumulative, 
        CASE
            WHEN (t.num_of_reads_delta + t.num_of_writes_delta) = 0 THEN 0
            ELSE (t.io_stall_read_ms_delta + t.io_stall_write_ms_delta) / (t.num_of_reads_delta + t.num_of_writes_delta)
        END                                                             AS io_stall_ms_per_transfer_delta, 
        CASE
            WHEN (t.num_of_reads_cumulative + t.num_of_writes_cumulative) = 0 THEN 0
            ELSE (t.io_stall_read_ms_cumulative + t.io_stall_write_ms_cumulative) / (t.num_of_reads_cumulative + t.num_of_writes_cumulative)
        END                                                             AS io_stall_ms_per_transfer_cumulative 
    FROM
    (
        SELECT 
            fs1.interval_id, fs1.interval_time_id, fs1.first_snapshot_id, fs1.last_snapshot_id, 
            -- Convert all datetimeoffset values to UTC datetime values before returning to Reporting Services
            CONVERT (datetime, SWITCHOFFSET (CAST (fs1.first_collection_time AS datetimeoffset(7)), '+00:00')) AS first_collection_time, 
            CONVERT (datetime, SWITCHOFFSET (CAST (fs2.first_collection_time AS datetimeoffset(7)), '+00:00')) AS last_collection_time, 
            CONVERT (datetime, SWITCHOFFSET (CAST (fs1.interval_start_time AS datetimeoffset(7)), '+00:00')) AS interval_start, 
            CONVERT (datetime, SWITCHOFFSET (CAST (fs2.interval_start_time AS datetimeoffset(7)), '+00:00')) AS interval_end, 
            fs2.database_name, fs2.database_id, fs2.logical_file_name, fs2.[file_id], fs2.type_desc, fs2.logical_disk, 
            -- All file stats will be reset to zero by a service cycle, which will cause 
            -- (snapshot2_io_time-snapshot1_io_time) calculations to produce an incorrect 
            -- negative wait time for the interval.  Detect this and avoid calculating 
            -- negative IO wait time deltas. 
            /***** READS ****/
            CASE 
                WHEN (fs2.num_of_reads - fs1.num_of_reads) < 0 THEN fs2.num_of_reads
                ELSE (fs2.num_of_reads - fs1.num_of_reads)
            END AS num_of_reads_delta,                                              -- num_of_reads_delta
            fs2.num_of_reads AS num_of_reads_cumulative,                            -- num_of_reads_cumulative
            CASE 
                WHEN (fs2.num_of_bytes_read - fs1.num_of_bytes_read) < 0 THEN fs2.num_of_bytes_read 
                ELSE (fs2.num_of_bytes_read - fs1.num_of_bytes_read)
            END / 1024 / 1024 AS num_of_mb_read_delta,                              -- num_of_mb_read_delta
            fs2.num_of_bytes_read /1024/1024 AS num_of_mb_read_cumulative,          -- num_of_mb_read_cumulative
            CASE
                WHEN (fs2.io_stall_read_ms - fs1.io_stall_read_ms) < 0 THEN fs2.io_stall_read_ms
                ELSE (fs2.io_stall_read_ms - fs1.io_stall_read_ms)
            END AS io_stall_read_ms_delta,                                          -- io_stall_read_ms_delta
            fs2.io_stall_read_ms AS io_stall_read_ms_cumulative,                    -- io_stall_read_ms_cumulative
            /**** WRITES ****/
            CASE
                WHEN (fs2.num_of_writes - fs1.num_of_writes) < 0 THEN fs2.num_of_writes
                ELSE (fs2.num_of_writes - fs1.num_of_writes)
            END AS num_of_writes_delta,                                             -- num_of_writes_delta
            fs2.num_of_writes AS num_of_writes_cumulative,                          -- num_of_writes_cumulative
            CASE
                WHEN (fs2.num_of_bytes_written - fs1.num_of_bytes_written) < 0 THEN fs2.num_of_bytes_written
                ELSE (fs2.num_of_bytes_written - fs1.num_of_bytes_written)
            END / 1024 / 1024 AS num_of_mb_written_delta,                           -- num_of_mb_written_delta
            fs2.num_of_bytes_written /1024/1024 AS num_of_mb_written_cumulative,    -- num_of_mb_written_cumulative
            CASE
                WHEN (fs2.io_stall_write_ms - fs1.io_stall_write_ms) < 0 THEN fs2.io_stall_write_ms
                ELSE (fs2.io_stall_write_ms - fs1.io_stall_write_ms)
            END AS io_stall_write_ms_delta,                                         -- io_stall_write_ms_delta
            fs2.io_stall_write_ms AS io_stall_write_ms_cumulative,                  -- io_stall_write_ms_cumulative
            fs1.size_on_disk_bytes / 1024 / 1024 AS size_on_disk_mb_interval_start, -- size_on_disk_mb_interval_start
            fs2.size_on_disk_bytes / 1024 / 1024 AS size_on_disk_mb_interval_end    -- size_on_disk_mb_interval_end
        FROM #file_stats AS fs1
        -- Self-join - fs1 represents IO stats at the beginning of the sample interval, while fs2 
        -- shows file stats at the end of the interval. 
        INNER JOIN #file_stats AS fs2 ON fs1.database_id = fs2.database_id AND fs1.[file_id] = fs2.[file_id] AND fs1.interval_id = fs2.interval_id-1 

    ) AS t
    ORDER BY t.database_name, t.logical_file_name, t.last_collection_time
END
GO


--
-- snapshots.rpt_list_all_servers
--  Shows a list of the servers that have data in this MDW database, plus information on how up-to-date 
--  the data is.  Used in the multi-server overview report. 
-- Parameters: 
--
IF (NOT OBJECT_ID(N'snapshots.rpt_list_all_servers', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[rpt_list_all_servers] ...'
    DROP PROCEDURE [snapshots].[rpt_list_all_servers]
END
GO 

PRINT 'Creating procedure [snapshots].[rpt_list_all_servers] ...'
GO
CREATE PROCEDURE [snapshots].[rpt_list_all_servers]
AS
BEGIN
    SET NOCOUNT ON;

    SELECT 
        instance_name, 
        query_statistics_last_upload, 
        disk_usage_last_upload, 
        server_activity_last_upload
    FROM 
    (
        SELECT 
            -- Name of the SQL Server instance
            instance_name, 
            -- Name of the collection set.  The names in the syscollector_collection_sets table can be localized. 
            -- We use well-known strings here because we want to defer selection of the appropriate localized 
            -- string to the client. 
            CASE 
                collection_set_uid
                WHEN '2DC02BD6-E230-4C05-8516-4E8C0EF21F95' THEN 'query_statistics_last_upload'
                WHEN '7B191952-8ECF-4E12-AEB2-EF646EF79FEF' THEN 'disk_usage_last_upload'
                WHEN '49268954-4FD4-4EB6-AA04-CD59D9BB5714' THEN 'server_activity_last_upload'
                ELSE NULL -- custom Collection set, not displayed on this report
            END AS top_level_report_name,
            -- Convert datetimeoffset to UTC datetime for RS 2005 compatibility 
            CONVERT (datetime, SWITCHOFFSET (MAX (snapshot_time), '+00:00')) AS latest_snapshot_time 
        FROM core.snapshots 
        -- For this report, only system collection sets matter
        WHERE collection_set_uid IN ('2DC02BD6-E230-4C05-8516-4E8C0EF21F95', '7B191952-8ECF-4E12-AEB2-EF646EF79FEF', '49268954-4FD4-4EB6-AA04-CD59D9BB5714')
        GROUP BY instance_name, collection_set_uid
    ) AS instance_report_list 
    PIVOT 
    (
        MAX (latest_snapshot_time)
        FOR top_level_report_name IN (query_statistics_last_upload, disk_usage_last_upload, server_activity_last_upload)
    ) AS pvt 
    ORDER BY instance_name;

END;
GO




/**********************************************************************/
/* CUSTOM_SNAPSHOTS SCHEMA                                            */
/**********************************************************************/
PRINT ''
PRINT 'Create schema custom_snpshots...'
GO
IF (SCHEMA_ID('custom_snapshots') IS NULL)
BEGIN
    DECLARE @sql nvarchar(128)
    SET @sql = 'CREATE SCHEMA custom_snapshots'
    EXEC sp_executesql @sql
END
GO




/**********************************************************************/
/* Create MDW security roles                                          */
/**********************************************************************/
PRINT ''
PRINT 'Create mdw_admin role...'
IF ( NOT EXISTS (SELECT * FROM sys.database_principals 
                    WHERE name = N'mdw_admin' AND type = 'R'))
BEGIN
    CREATE ROLE [mdw_admin]
END
ELSE -- if the role exists check to see if it has members
BEGIN
    IF NOT EXISTS (SELECT rm.member_principal_id
                FROM sys.database_principals dp 
                INNER JOIN sys.database_role_members rm ON rm.role_principal_id = dp.principal_id
                WHERE name = N'mdw_admin' AND type = 'R')
    BEGIN
        -- if the role has no members drop and recreate it
        DROP ROLE [mdw_admin]
        CREATE ROLE [mdw_admin]
    END
END
GO

PRINT 'Create mdw_writer role...'
IF ( NOT EXISTS (SELECT * FROM sys.database_principals 
                    WHERE name = N'mdw_writer' AND type = 'R'))
BEGIN
    CREATE ROLE [mdw_writer]
END
ELSE -- if the role exists check to see if it has members
BEGIN
    IF NOT EXISTS (SELECT rm.member_principal_id
                FROM sys.database_principals dp 
                INNER JOIN sys.database_role_members rm ON rm.role_principal_id = dp.principal_id
                WHERE name = N'mdw_writer' AND type = 'R')
    BEGIN
        -- if the role has no members drop and recreate it
        DROP ROLE [mdw_writer]
        CREATE ROLE [mdw_writer]
    END
END
GO

PRINT 'Create mdw_reader role...'
IF ( NOT EXISTS (SELECT * FROM sys.database_principals 
                    WHERE name = N'mdw_reader' AND type = 'R'))
BEGIN
    CREATE ROLE [mdw_reader]
END
ELSE -- if the role exists check to see if it has members
BEGIN
    IF NOT EXISTS (SELECT rm.member_principal_id
                FROM sys.database_principals dp 
                INNER JOIN sys.database_role_members rm ON rm.role_principal_id = dp.principal_id
                WHERE name = N'mdw_reader' AND type = 'R')
    BEGIN
        -- if the role has no members drop and recreate it
        DROP ROLE [mdw_reader]
        CREATE ROLE [mdw_reader]
    END
END
GO

-- permissions of mdw_writer and mdw_reader are inclusive to mdw_admin
EXECUTE sp_addrolemember @rolename = 'mdw_writer' , 
                   @membername = 'mdw_admin' 
GO

EXECUTE sp_addrolemember @rolename = 'mdw_reader' , 
                   @membername = 'mdw_admin' 
GO

PRINT ''
PRINT 'Create loginless user ...'
IF (NOT EXISTS(SELECT * FROM sys.database_principals WHERE NAME = 'mdw_check_operator_admin'))
BEGIN
	CREATE USER [mdw_check_operator_admin] WITHOUT LOGIN
END
GO

EXECUTE sp_addrolemember @rolename = 'mdw_admin', @membername = 'mdw_check_operator_admin' 
GO


/**********************************************************************/
/* Setup permissions                                                  */
/**********************************************************************/
PRINT ''
PRINT 'Granting permissions to MDW security roles...'

-- Database level permissions
GRANT VIEW DEFINITION                                           TO [mdw_writer]

-- Core schema permissions
GRANT EXECUTE ON [core].[sp_create_snapshot]                    TO [mdw_writer]
GRANT EXECUTE ON [core].[sp_update_data_source]                 TO [mdw_writer]
GRANT EXECUTE ON [core].[sp_add_collector_type]                 TO [mdw_admin]
GRANT EXECUTE ON [core].[sp_remove_collector_type]              TO [mdw_admin]
GRANT EXECUTE ON [core].[sp_purge_data]                         TO [mdw_admin]
GRANT EXECUTE ON [core].[sp_stop_purge]                         TO [mdw_admin]

GRANT REFERENCES ON [core].[fn_check_operator]                  TO [mdw_check_operator_admin]

GRANT SELECT  ON [core].[snapshots]                             TO [mdw_admin]    
GRANT SELECT  ON [core].[snapshots]                             TO [mdw_writer]    
GRANT SELECT  ON [core].[snapshots]                             TO [mdw_reader]    

GRANT SELECT  ON [core].[supported_collector_types]             TO [mdw_admin]
GRANT SELECT  ON [core].[supported_collector_types]             TO [mdw_writer]
GRANT SELECT  ON [core].[supported_collector_types]             TO [mdw_reader]

GRANT SELECT  ON [core].[wait_categories]                       TO [mdw_reader]
GRANT SELECT  ON [core].[wait_types]                            TO [mdw_reader]
GRANT SELECT  ON [core].[wait_types_categorized]                TO [mdw_reader]

GRANT SELECT  ON [core].[snapshots_internal]                    TO [mdw_admin]
GRANT SELECT  ON [core].[source_info_internal]                  TO [mdw_admin]
GRANT SELECT  ON [core].[snapshot_timetable_internal]           TO [mdw_admin]
GRANT SELECT  ON [core].[supported_collector_types_internal]    TO [mdw_admin]
GRANT SELECT  ON [core].[fn_query_text_from_handle]             TO [mdw_reader]

GRANT SELECT  ON [core].[performance_counter_report_group_items] TO [mdw_reader]

-- Snapshot schema permissions
GRANT INSERT  ON SCHEMA :: [snapshots]                          TO [mdw_writer]
GRANT EXECUTE ON SCHEMA :: [snapshots]                          TO [mdw_writer]
GRANT DELETE  ON SCHEMA :: [snapshots]                          TO [mdw_admin]
GRANT SELECT  ON SCHEMA :: [snapshots]                          TO [mdw_reader]
-- mdw_writer needs SELECT permission to perform upload because bcp selects the table to verify its schema
GRANT SELECT  ON SCHEMA :: [snapshots]                          TO [mdw_writer]

GRANT EXECUTE ON [snapshots].[sp_get_unknown_query_plan]        TO [mdw_writer]
GRANT EXECUTE ON [snapshots].[sp_get_unknown_query_text]        TO [mdw_writer]
GRANT EXECUTE ON [snapshots].[sp_update_query_plan]             TO [mdw_writer]
GRANT EXECUTE ON [snapshots].[sp_update_query_text]             TO [mdw_writer]

-- Custom_snapshot schema permissions
GRANT CREATE TABLE                                              TO [mdw_writer]
GRANT ALTER   ON SCHEMA :: [custom_snapshots]                   TO [mdw_writer]
GRANT INSERT  ON SCHEMA :: [custom_snapshots]                   TO [mdw_writer]
GRANT DELETE  ON SCHEMA :: [custom_snapshots]                   TO [mdw_admin]
GRANT SELECT  ON SCHEMA :: [custom_snapshots]                   TO [mdw_reader]
-- mdw_writer needs SELECT permission to perform upload because bcp selects the table to verify its schema
GRANT SELECT  ON SCHEMA :: [custom_snapshots]                   TO [mdw_writer]

GRANT CREATE TABLE                                              TO [mdw_admin]
GRANT CONTROL ON SCHEMA :: [custom_snapshots]                   TO [mdw_admin]

-- Report procedures
GRANT EXECUTE ON [snapshots].[rpt_snapshot_times]                       TO [mdw_reader]
GRANT EXECUTE ON [snapshots].[rpt_interval_collection_times]            TO [mdw_reader]
GRANT EXECUTE ON [snapshots].[rpt_next_and_previous_collection_times]   TO [mdw_reader]
GRANT EXECUTE ON [snapshots].[rpt_generic_perfmon]                      TO [mdw_reader]
GRANT EXECUTE ON [snapshots].[rpt_generic_perfmon_pivot]                TO [mdw_reader]
GRANT EXECUTE ON [snapshots].[rpt_wait_stats]                           TO [mdw_reader]
GRANT EXECUTE ON [snapshots].[fn_hexstrtovarbin]                        TO [mdw_reader]
GRANT EXECUTE ON [snapshots].[rpt_top_query_stats]                      TO [mdw_reader]
GRANT EXECUTE ON [snapshots].[rpt_query_stats]                          TO [mdw_reader]
GRANT EXECUTE ON [snapshots].[rpt_query_plan_stats]                     TO [mdw_reader]
GRANT EXECUTE ON [snapshots].[rpt_query_plan_stats_timeline]            TO [mdw_reader]
GRANT EXECUTE ON [snapshots].[rpt_query_plan_missing_indexes]           TO [mdw_reader]
GRANT EXECUTE ON [snapshots].[rpt_query_plan_parameters]                TO [mdw_reader]
GRANT EXECUTE ON [snapshots].[rpt_query_plan_details]                   TO [mdw_reader]
GRANT EXECUTE ON [snapshots].[rpt_blocking_chains]                      TO [mdw_reader]
GRANT EXECUTE ON [snapshots].[rpt_blocking_chain_detail]                TO [mdw_reader]
GRANT EXECUTE ON [snapshots].[rpt_active_sessions_and_requests]         TO [mdw_reader]
GRANT EXECUTE ON [snapshots].[rpt_sql_memory_clerks]                    TO [mdw_reader]
GRANT EXECUTE ON [snapshots].[rpt_sql_process_and_system_memory]        TO [mdw_reader]
GRANT EXECUTE ON [snapshots].[rpt_sampled_waits]                        TO [mdw_reader]
GRANT EXECUTE ON [snapshots].[rpt_sampled_waits_longest]                TO [mdw_reader]
GRANT EXECUTE ON [snapshots].[rpt_sampled_waits_hottest_resources]      TO [mdw_reader]
GRANT EXECUTE ON [snapshots].[rpt_io_virtual_file_stats]                TO [mdw_reader]
GRANT EXECUTE ON [snapshots].[rpt_list_all_servers]                     TO [mdw_reader]

-- NOTE: In order to insert data into MDW the login used for uploading data
-- needs to have ADMINISTER BULK OPERATIONS server permission granted

GO

/**********************************************************************/
/* LEGACY OBJECTS - KEPT FOR OLDER CTPs BUT NO LONGER USED            */
/**********************************************************************/

/**********************************************************************/
/* Used by CTP5 (but not CTP6):                                       */
/**********************************************************************/

--
-- snapshots.rpt_waiting_sessions
--  Returns list of sessions waiting for specified wait type category
-- Parameters: 
--    @ServerName - SQL Server instance name
--    @EndTime - end of the time window (UTC)
--    @WindowSize - number of minutes in the time window
--    @CategoryName - Name of wait category (optional)
--
IF (NOT OBJECT_ID(N'snapshots.rpt_waiting_sessions', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[rpt_waiting_sessions] ...'
    DROP PROCEDURE [snapshots].[rpt_waiting_sessions]
END
GO 

PRINT 'Creating procedure [snapshots].[rpt_waiting_sessions] ...'
GO
CREATE PROCEDURE [snapshots].[rpt_waiting_sessions]
    @ServerName sysname, 
    @EndTime datetime, 
    @WindowSize int, 
    @CategoryName nvarchar(20)
AS
BEGIN
    SET NOCOUNT ON;
    DECLARE @start_time_internal datetimeoffset(7);
    DECLARE @end_time_internal datetimeoffset(7);
    
    -- Start time should be passed in as a UTC datetime
    IF (@EndTime IS NOT NULL)
    BEGIN
        -- Assumed time zone offset for this conversion is +00:00 (datetime must be passed in as UTC)
        SET @end_time_internal = CAST (@EndTime AS datetimeoffset(7));
    END
    ELSE BEGIN
        SELECT @end_time_internal = MAX(ar.collection_time)
        FROM core.snapshots AS s
        INNER JOIN snapshots.active_sessions_and_requests AS ar ON s.snapshot_id = ar.snapshot_id
        WHERE s.instance_name = @ServerName -- AND collection_set_uid = '49268954-4FD4-4EB6-AA04-CD59D9BB5714' -- Server Activity CS
    END
    SET @start_time_internal = DATEADD (minute, -1 * @WindowSize, @end_time_internal);
    
    DECLARE @total_waits TABLE
    (
        wait_type nvarchar(45),
        wait_count bigint, 
        wait_duration_ms bigint
    )

    INSERT INTO @total_waits
    SELECT 
        ar.wait_type,
        COUNT(*) AS wait_count, 
        SUM (wait_duration_ms)
    FROM snapshots.active_sessions_and_requests ar
    JOIN core.snapshots s ON (s.snapshot_id = ar.snapshot_id)
    WHERE s.instance_name = @ServerName -- AND collection_set_uid = '49268954-4FD4-4EB6-AA04-CD59D9BB5714' -- Server Activity CS
        AND ar.collection_time BETWEEN @start_time_internal AND @end_time_internal
        AND ar.wait_type != ''
    GROUP BY ar.wait_type
    
    SELECT
        t.source_id,
        t.session_id,
        t.request_id,
        t.database_name,
        CONVERT (datetime, SWITCHOFFSET (CAST (t.login_time AS datetimeoffset(7)), '+00:00')) AS login_time, 
        t.login_name,
        t.[program_name],
        t.sql_handle,
        master.dbo.fn_varbintohexstr (t.sql_handle) AS sql_handle_str,
        t.statement_start_offset,
        t.statement_end_offset,
        t.plan_handle,
        master.dbo.fn_varbintohexstr (t.plan_handle) AS plan_handle_str,
        t.wait_type,
        t.wait_count,
        t.wait_total_percent,
        t.wait_type_wait_percent,
        qt.query_text, 
        REPLACE (REPLACE (REPLACE (REPLACE (REPLACE (REPLACE (
            LEFT (LTRIM (qt.query_text), 100)
            , CHAR(9), ' '), CHAR(10), ' '), CHAR(13), ' '), '   ', ' '), '  ', ' '), '  ', ' ') AS flat_query_text
    FROM
    (
        SELECT 
            s.source_id,
            ar.session_id,
            ar.request_id,
            ar.database_name,
            ar.login_time,
            ar.login_name,
            ar.program_name,
            ar.sql_handle,
            ar.statement_start_offset,
            ar.statement_end_offset,
            ar.plan_handle,
            ar.wait_type,
            COUNT(*) AS wait_count,
            (COUNT(*)) / CONVERT(decimal, (SELECT SUM(wait_count) FROM @total_waits)) AS wait_total_percent,
            (COUNT(*)) / CONVERT(decimal, (SELECT wait_count FROM @total_waits WHERE wait_type = ar.wait_type)) AS wait_type_wait_percent
        FROM snapshots.active_sessions_and_requests AS ar
        INNER JOIN core.snapshots AS s ON (s.snapshot_id = ar.snapshot_id)
        INNER JOIN core.wait_types_categorized AS wt on (wt.wait_type = ar.wait_type)
        WHERE s.instance_name = @ServerName -- AND s.collection_set_uid = '49268954-4FD4-4EB6-AA04-CD59D9BB5714' -- Server Activity CS
            AND ar.collection_time BETWEEN @start_time_internal AND @end_time_internal
            AND wt.category_name = @CategoryName
        GROUP BY s.source_id, ar.session_id, ar.request_id, ar.database_name, ar.login_time, ar.login_name, ar.program_name, 
            ar.wait_type, ar.sql_handle, ar.statement_start_offset, ar.statement_end_offset, ar.plan_handle
    ) AS t 
    OUTER APPLY snapshots.fn_get_query_text(t.source_id, t.sql_handle, t.statement_start_offset, t.statement_end_offset) AS qt

END;
GO
GRANT EXECUTE ON [snapshots].[rpt_waiting_sessions]                     TO [mdw_reader]
GO


-- This function returns a list of all the known query plans from a disctinct source 
IF (NOT OBJECT_ID(N'snapshots.fn_get_notable_query_plans', 'TF') IS NULL)
BEGIN
    PRINT 'Dropping function [snapshots].[fn_get_notable_query_plans] ...'
    DROP FUNCTION [snapshots].[fn_get_notable_query_plans]
END

PRINT 'Creating function [snapshots].[fn_get_notable_query_plans]'
Go
CREATE FUNCTION [snapshots].[fn_get_notable_query_plans](
    @source_id  int
)
RETURNS @notable_queries TABLE (sql_handle varbinary(64) , 
                                plan_handle varbinary(64), 
                                statement_start_offset int, 
                                statement_end_offset int,
                                creation_time datetime)
BEGIN
    INSERT INTO @notable_queries
    SELECT  [sql_handle], 
        [plan_handle],
	    [statement_start_offset],
	    [statement_end_offset],
            -- Convert datetimeoffset to datetime so that SSIS can easily join the output back 
            -- to the new sys.dm_exec_query_stats data
            CONVERT (datetime, [creation_time]) AS [creation_time]
    FROM    [snapshots].[notable_query_plan]
    WHERE   [source_id] = @source_id
    ORDER BY [sql_handle] ASC, [plan_handle], [statement_start_offset], [statement_end_offset], [creation_time] ASC

    RETURN
END
GO

GRANT SELECT  ON [snapshots].[fn_get_notable_query_plans]       TO [mdw_writer]
GO



-- This function returns a list of all the known sql_handles (i.e., query text) from a disctinct source 
IF (NOT OBJECT_ID(N'snapshots.fn_get_notable_query_text', 'TF') IS NULL)
BEGIN
    PRINT 'Dropping function [snapshots].[fn_get_notable_query_text] ...'
    DROP FUNCTION [snapshots].[fn_get_notable_query_text]
END

PRINT 'Creating function [snapshots].[fn_get_notable_query_text]'
Go
CREATE FUNCTION [snapshots].[fn_get_notable_query_text](
    @source_id int
)
RETURNS @notable_text TABLE (sql_handle varbinary(64))
BEGIN
    INSERT INTO @notable_text
    SELECT  [sql_handle]
    FROM    [snapshots].[notable_query_text]
    WHERE   [source_id] = @source_id
    ORDER BY [sql_handle] ASC

    RETURN
END
GO

GRANT SELECT  ON [snapshots].[fn_get_notable_query_text]        TO [mdw_writer]
GO


--
-- snapshots.rpt_sql_memory_counters_one_snapshot
--  Returns values for page life expectancy and other SQL Server memory counters
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @snapshot_time_id - time window identifier (snapshot ID from core.snapshots) 
--
IF (NOT OBJECT_ID(N'snapshots.rpt_sql_memory_counters_one_snapshot', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[rpt_sql_memory_counters_one_snapshot] ...'
    DROP PROCEDURE [snapshots].[rpt_sql_memory_counters_one_snapshot]
END
GO 

PRINT 'Creating procedure [snapshots].[rpt_sql_memory_counters_one_snapshot] ...'
GO
CREATE PROCEDURE [snapshots].[rpt_sql_memory_counters_one_snapshot]
    @instance_name sysname,
    @snapshot_time_id int
AS
BEGIN
    SET NOCOUNT ON;
    
    SELECT 
        pc.performance_counter_name AS series,
        CONVERT (datetime, SWITCHOFFSET (CAST (pc.collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        pc.formatted_value 
    FROM snapshots.performance_counters pc
    INNER JOIN core.snapshots s ON (s.snapshot_id = pc.snapshot_id)
    WHERE s.instance_name = @instance_name
        AND s.snapshot_time_id = @snapshot_time_id
        AND pc.performance_object_name LIKE '%SQL%:Buffer Manager'
        AND pc.performance_counter_name = 'Page life expectancy'
    ORDER BY 
        pc.collection_time, pc.performance_counter_name
END;
GO

GRANT EXECUTE ON [snapshots].[rpt_sql_memory_counters_one_snapshot]     TO [mdw_reader]
GO


--
-- snapshots.rpt_sql_memory_clerks_one_snapshot
--  Returns data from os_memory_clerks table
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @snapshot_time_id - time window identifier (snapshot ID from core.snapshots) 
--
IF (NOT OBJECT_ID(N'snapshots.rpt_sql_memory_clerks_one_snapshot', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[rpt_sql_memory_clerks_one_snapshot] ...'
    DROP PROCEDURE [snapshots].[rpt_sql_memory_clerks_one_snapshot]
END
GO 

PRINT 'Creating procedure [snapshots].[rpt_sql_memory_clerks_one_snapshot] ...'
GO
CREATE PROCEDURE [snapshots].[rpt_sql_memory_clerks_one_snapshot]
    @instance_name sysname,
    @snapshot_time_id int
AS
BEGIN
    SET NOCOUNT ON;

    DECLARE @total_allocated TABLE (
        collection_time datetimeoffset(7),
        total_kb bigint
    );

    INSERT INTO @total_allocated 
    SELECT 
        mc.collection_time,
        SUM(mc.single_pages_kb + 
            mc.multi_pages_kb + 
            (CASE WHEN type <> N'MEMORYCLERK_SQLBUFFERPOOL' THEN mc.virtual_memory_committed_kb 
            ELSE 0 END) + 
            mc.shared_memory_committed_kb)
    FROM snapshots.os_memory_clerks mc
    JOIN core.snapshots s ON (s.snapshot_id = mc.snapshot_id)
    WHERE s.instance_name = @instance_name
        AND s.snapshot_time_id = @snapshot_time_id
    GROUP BY mc.collection_time

    SELECT 
        CONVERT (datetime, SWITCHOFFSET (CAST (mc.collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        mc.type,
        mc.single_pages_kb + mc.multi_pages_kb as allocated_kb,
        mc.virtual_memory_reserved_kb as virtual_reserved_kb,
        mc.virtual_memory_committed_kb as virtual_committed_kb,
        mc.awe_allocated_kb as awe_allocated_kb,
        mc.shared_memory_reserved_kb as shared_reserved_kb,
        mc.shared_memory_committed_kb as shared_committed_kb,
        (mc.single_pages_kb + mc.multi_pages_kb + (CASE WHEN type <> 'MEMORYCLERK_SQLBUFFERPOOL' THEN mc.virtual_memory_committed_kb ELSE 0 END) + mc.shared_memory_committed_kb) as total_kb,
        (mc.single_pages_kb + mc.multi_pages_kb + (CASE WHEN type <> 'MEMORYCLERK_SQLBUFFERPOOL' THEN mc.virtual_memory_committed_kb ELSE 0 END) + mc.shared_memory_committed_kb) / CONVERT(decimal, ta.total_kb) AS percent_total_kb,
        CASE
            WHEN (mc.single_pages_kb + mc.multi_pages_kb + (CASE WHEN type <> 'MEMORYCLERK_SQLBUFFERPOOL' THEN mc.virtual_memory_committed_kb ELSE 0 END) + mc.shared_memory_committed_kb) / CONVERT(decimal, ta.total_kb) > 0.05 THEN mc.type
            ELSE N'Other'
        END AS graph_type
    FROM snapshots.os_memory_clerks mc
    JOIN @total_allocated ta ON (mc.collection_time = ta.collection_time)
    ORDER BY collection_time
    
END;
GO

GRANT EXECUTE ON [snapshots].[rpt_sql_memory_clerks_one_snapshot]       TO [mdw_reader]
GO


--
-- snapshots.rpt_sql_process_memory_one_snapshot
--  Returns details on sql process memory usage
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @snapshot_time_id - time window identifier (snapshot ID from core.snapshots) 
--
IF (NOT OBJECT_ID(N'snapshots.rpt_sql_process_memory_one_snapshot', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[rpt_sql_process_memory_one_snapshot] ...'
    DROP PROCEDURE [snapshots].[rpt_sql_process_memory_one_snapshot]
END
GO 

PRINT 'Creating procedure [snapshots].[rpt_sql_process_memory_one_snapshot] ...'
GO
CREATE PROCEDURE [snapshots].[rpt_sql_process_memory_one_snapshot]
    @instance_name sysname,
    @snapshot_time_id int
AS
BEGIN
    SET NOCOUNT ON;

    SELECT 
        CONVERT (datetime, SWITCHOFFSET (CAST (collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        CASE 
            WHEN series = 'virtual_address_space_reserved_kb' THEN 'Virtual Memory Reserved'
            WHEN series = 'virtual_address_space_committed_kb' THEN 'Virtual Memory Committed'
            WHEN series = 'physical_memory_in_use_kb' THEN 'Physical Memory In Use'
            WHEN series = 'process_physical_memory_low' THEN 'Physical Memory Low'
            WHEN series = 'process_virtual_memory_low' THEN 'Virtual Memory Low'
            ELSE series
        END AS series,
        [value] / 1024 AS [value] -- convert KB to MB
    FROM 
    (
        SELECT 
            pm.collection_time,
            pm.virtual_address_space_reserved_kb,
            pm.virtual_address_space_committed_kb,
            pm.physical_memory_in_use_kb,
            CONVERT (bigint, pm.process_physical_memory_low) AS process_physical_memory_low,
            CONVERT (bigint, pm.process_virtual_memory_low) AS process_virtual_memory_low
        FROM [snapshots].[os_process_memory] pm
        JOIN core.snapshots s ON (s.snapshot_id = pm.snapshot_id)
        WHERE s.instance_name = @instance_name
            AND s.snapshot_time_id = @snapshot_time_id
    ) AS pvt
    UNPIVOT
    (
        [value] for [series] in 
            (virtual_address_space_reserved_kb, virtual_address_space_committed_kb, 
            physical_memory_in_use_kb)
    ) AS unpvt
    UNION ALL 
    SELECT 
        CONVERT (datetime, SWITCHOFFSET (CAST (collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        'Stolen Buffer Pool' AS [series], 
        pc.formatted_value * 8 AS [value] -- Convert from pages to KB
    FROM snapshots.performance_counters AS pc
    INNER JOIN core.snapshots s ON (s.snapshot_id = pc.snapshot_id)
    WHERE s.instance_name = @instance_name
        AND s.snapshot_time_id = @snapshot_time_id
        AND pc.performance_object_name LIKE '%SQL%:Buffer Manager'
        AND pc.performance_counter_name = 'Stolen pages'
    ORDER BY collection_time
END
GO

GRANT EXECUTE ON [snapshots].[rpt_sql_process_memory_one_snapshot]      TO [mdw_reader]
GO

--
-- snapshots.rpt_memory_counters_one_snapshot
--  Returns values for memory usage counters
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @snapshot_time_id - time window identifier (snapshot ID from core.snapshots) 
--
IF (NOT OBJECT_ID(N'snapshots.rpt_memory_counters_one_snapshot', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[rpt_memory_counters_one_snapshot] ...'
    DROP PROCEDURE [snapshots].[rpt_memory_counters_one_snapshot]
END
GO 

PRINT 'Creating procedure [snapshots].[rpt_memory_counters_one_snapshot] ...'
GO
CREATE PROCEDURE [snapshots].[rpt_memory_counters_one_snapshot]
    @instance_name sysname,
    @snapshot_time_id int
AS
BEGIN
    SET NOCOUNT ON;
    
    SELECT 
        pc.performance_counter_name AS series,
        CONVERT (datetime, SWITCHOFFSET (CAST (pc.collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        pc.collection_time,
        pc.formatted_value / (1024*1024) AS formatted_value
    FROM snapshots.performance_counters pc
    INNER JOIN core.snapshots s ON (s.snapshot_id = pc.snapshot_id)
    WHERE s.instance_name = @instance_name
        AND s.snapshot_time_id = @snapshot_time_id
        AND 
        (
            (pc.performance_object_name = 'Memory' AND pc.performance_counter_name IN ('Cache Bytes', 'Pool Nonpaged Bytes'))
            OR (pc.performance_object_name = 'Process' AND pc.performance_counter_name = 'Working Set' AND pc.performance_instance_name = '_Total')
        )
    ORDER BY 
        pc.collection_time, series
END;
GO

GRANT EXECUTE ON [snapshots].[rpt_memory_counters_one_snapshot]         TO [mdw_reader]
GO


--
-- snapshots.rpt_memory_rates_one_snapshot
--  Returns values for various memory ratios 
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @snapshot_time_id - time window identifier (snapshot ID from core.snapshots) 
--
IF (NOT OBJECT_ID(N'snapshots.rpt_memory_rates_one_snapshot', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[rpt_memory_rates_one_snapshot] ...'
    DROP PROCEDURE [snapshots].[rpt_memory_rates_one_snapshot]
END
GO 

PRINT 'Creating procedure [snapshots].[rpt_memory_rates_one_snapshot] ...'
GO
CREATE PROCEDURE [snapshots].[rpt_memory_rates_one_snapshot]
    @instance_name sysname,
    @snapshot_time_id int
AS
BEGIN
    SET NOCOUNT ON;
    
    SELECT 
        pc.performance_counter_name AS series,
        CONVERT (datetime, SWITCHOFFSET (CAST (pc.collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        pc.collection_time,
        pc.formatted_value
    FROM snapshots.performance_counters pc
    INNER JOIN core.snapshots s ON (s.snapshot_id = pc.snapshot_id)
    WHERE s.instance_name = @instance_name
        AND s.snapshot_time_id = @snapshot_time_id
        AND pc.performance_object_name = 'Memory' 
        AND pc.performance_counter_name IN ('Page Faults/sec', 'Page Reads/sec', 'Page Writes/sec', 'Cache Faults/sec')
    ORDER BY 
        pc.collection_time, series
END;
GO

GRANT EXECUTE ON [snapshots].[rpt_memory_rates_one_snapshot]            TO [mdw_reader]
GO

--
-- snapshots.rpt_cpu_counters_one_snapshot
--  Returns values for CPU usage counters per processor
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @snapshot_time_id - time window identifier (snapshot ID from core.snapshots) 
--
IF (NOT OBJECT_ID(N'snapshots.rpt_cpu_counters_one_snapshot', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[rpt_cpu_counters_one_snapshot] ...'
    DROP PROCEDURE [snapshots].[rpt_cpu_counters_one_snapshot]
END
GO 

PRINT 'Creating procedure [snapshots].[rpt_cpu_counters_one_snapshot] ...'
GO
CREATE PROCEDURE [snapshots].[rpt_cpu_counters_one_snapshot]
    @instance_name sysname,
    @snapshot_time_id int
AS
BEGIN
    SET NOCOUNT ON;
    
    SELECT 
        N'CPU ' + CONVERT (nvarchar(10), ISNULL(pc.performance_instance_name, N'')) as series,
        CONVERT (datetime, SWITCHOFFSET (CAST (pc.collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        pc.formatted_value
    FROM snapshots.performance_counters pc
    JOIN core.snapshots s ON (s.snapshot_id = pc.snapshot_id)
    WHERE s.instance_name = @instance_name
        AND s.snapshot_time_id = @snapshot_time_id
        AND pc.performance_object_name = 'Processor' AND pc.performance_counter_name = '% Processor Time' 
        AND pc.performance_instance_name != '_Total'
    ORDER BY 
        pc.collection_time, series
    -- These trace flags are necessary for a good plan, due to the join on ascending PK w/range filter
    OPTION (QUERYTRACEON 2389, QUERYTRACEON 2390)
END;
GO

GRANT EXECUTE ON [snapshots].[rpt_cpu_counters_one_snapshot]            TO [mdw_reader]
GO

--
-- snapshots.rpt_cpu_queues_one_snapshot
--  Returns values for queue length counter per processor 
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @snapshot_time_id - time window identifier (snapshot ID from core.snapshots) 
--
IF (NOT OBJECT_ID(N'snapshots.rpt_cpu_queues_one_snapshot', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[rpt_cpu_queues_one_snapshot] ...'
    DROP PROCEDURE [snapshots].[rpt_cpu_queues_one_snapshot]
END
GO 

PRINT 'Creating procedure [snapshots].[rpt_cpu_queues_one_snapshot] ...'
GO
CREATE PROCEDURE [snapshots].[rpt_cpu_queues_one_snapshot]
    @instance_name sysname,
    @snapshot_time_id int
AS
BEGIN
    SET NOCOUNT ON;
    
    SELECT 
        CASE pc.performance_counter_name 
            WHEN 'Processor Queue Length' THEN N'Processor Queue Length'
            ELSE N'CPU ' + CONVERT (nvarchar(10), ISNULL(pc.performance_instance_name, N'')) 
        END AS series,
        CONVERT (datetime, SWITCHOFFSET (CAST (pc.collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        pc.formatted_value
    FROM snapshots.performance_counters pc
    JOIN core.snapshots s ON (s.snapshot_id = pc.snapshot_id)
    WHERE s.instance_name = @instance_name
        AND s.snapshot_time_id = @snapshot_time_id
        AND 
        (
            (pc.performance_object_name = 'Server Work Queues' AND pc.performance_counter_name = 'Queue Length'
                AND pc.performance_instance_name != '_Total' AND ISNUMERIC (pc.performance_instance_name) = 1)
            OR (pc.performance_object_name = 'System' AND pc.performance_counter_name = 'Processor Queue Length')
        )
    ORDER BY 
        pc.collection_time, series
END;
GO

GRANT EXECUTE ON [snapshots].[rpt_cpu_queues_one_snapshot]              TO [mdw_reader]
GO

--
-- snapshots.rpt_cpu_usage_per_process
--  Returns min, max, avg CPU usage and avg thread count for top 10 processes
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @snapshot_time_id - time window identifier (snapshot ID from core.snapshots) 
--
IF (NOT OBJECT_ID(N'snapshots.rpt_cpu_usage_per_process', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[rpt_cpu_usage_per_process] ...'
    DROP PROCEDURE [snapshots].[rpt_cpu_usage_per_process]
END
GO 

PRINT 'Creating procedure [snapshots].[rpt_cpu_usage_per_process] ...'
GO
CREATE PROCEDURE [snapshots].[rpt_cpu_usage_per_process]
    @instance_name sysname,
    @snapshot_time_id int
AS
BEGIN
    SET NOCOUNT ON;
    
    -- Determine the CPU count on the target system by querying the number of "Processor" 
    -- counter instances we captured in a perfmon sample that was captured around the same 
    -- time. 
    DECLARE @cpu_count smallint
    SELECT @cpu_count = COUNT (DISTINCT pc.performance_instance_name)
    FROM snapshots.performance_counters AS pc
    INNER JOIN core.snapshots s ON s.snapshot_id = pc.snapshot_id 
    WHERE pc.performance_object_name = 'Processor' AND pc.performance_counter_name = '% Processor Time'
        AND pc.performance_instance_name != '_Total'
        AND s.snapshot_time_id = @snapshot_time_id
        AND s.instance_name = @instance_name
        AND pc.collection_time = 
            (SELECT TOP 1 collection_time FROM snapshots.performance_counter_values pcv2 WHERE pcv2.snapshot_id = s.snapshot_id)

    SELECT TOP 10
        cpu.process_name,
        cpu.minimum_value / @cpu_count AS cpu_minimum_value,
        cpu.maximum_value / @cpu_count AS cpu_maximum_value,
        cpu.average_value / @cpu_count AS cpu_average_value,
        tc.average_value AS  tc_average_value
    FROM
    (  SELECT
            ISNULL(pc.performance_instance_name, N'') AS process_name,
		    MIN(pc.formatted_value)		 AS minimum_value,
		    MAX(pc.formatted_value)		 AS maximum_value,
            AVG(pc.formatted_value)		 AS average_value
        FROM [snapshots].[performance_counters] AS pc
        INNER JOIN core.snapshots s ON (s.snapshot_id = pc.snapshot_id)
        WHERE
            s.snapshot_time_id = @snapshot_time_id
            AND s.instance_name = @instance_name
		    AND (pc.performance_object_name = 'Process' AND pc.performance_counter_name = '% Processor Time'
		        AND pc.performance_instance_name NOT IN ('_Total', 'Idle'))
        GROUP BY pc.performance_instance_name
    ) AS cpu
    INNER JOIN
    (  SELECT
            ISNULL(pc.performance_instance_name, N'') AS process_name,
		    MIN(pc.formatted_value)		 AS minimum_value,
		    MAX(pc.formatted_value)		 AS maximum_value,
            AVG(pc.formatted_value)		 AS average_value
        FROM [snapshots].[performance_counters] as pc
        INNER JOIN core.snapshots s ON (s.snapshot_id = pc.snapshot_id)
        WHERE
            s.snapshot_time_id = @snapshot_time_id
            AND s.instance_name = @instance_name
		    AND (pc.performance_object_name = 'Process' AND pc.performance_counter_name = 'Thread Count'
		        AND pc.performance_instance_name NOT IN ('_Total', 'Idle'))
        GROUP BY pc.performance_instance_name
    ) AS tc ON (tc.process_name = cpu.process_name)
    ORDER BY cpu_average_value DESC

END;
GO

GRANT EXECUTE ON [snapshots].[rpt_cpu_usage_per_process]                TO [mdw_reader]
GO

--
-- snapshots.rpt_sessions_and_connections
--  Returns counts for sessions and connections within the given snapshot
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @snapshot_time_id - time window identifier (snapshot ID from core.snapshots) 
--
IF (NOT OBJECT_ID(N'snapshots.rpt_sessions_and_connections', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[rpt_sessions_and_connections] ...'
    DROP PROCEDURE [snapshots].[rpt_sessions_and_connections]
END
GO 

PRINT 'Creating procedure [snapshots].[rpt_sessions_and_connections] ...'
GO
CREATE PROCEDURE [snapshots].[rpt_sessions_and_connections]
    @instance_name sysname,
    @snapshot_time_id int
AS
BEGIN
    SET NOCOUNT ON;


    SELECT 
        pc.performance_counter_name AS series,
        CONVERT (datetime, SWITCHOFFSET (CAST (pc.collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        pc.formatted_value
    FROM snapshots.performance_counters pc
    INNER JOIN core.snapshots s ON (s.snapshot_id = pc.snapshot_id)
    WHERE s.instance_name = @instance_name
        AND s.snapshot_time_id = @snapshot_time_id
        AND (pc.performance_object_name LIKE '%SQL%:General Statistics' OR pc.performance_object_name LIKE '%SQL%:Databases')
        AND pc.performance_counter_name IN ('User Connections', 'Active Transactions')

    UNION ALL 

    SELECT 
        N'Active sessions' AS series,
        ar.collection_time,
        COUNT(DISTINCT ar.session_id) AS formatted_value
    FROM snapshots.active_sessions_and_requests ar
    INNER JOIN core.snapshots s ON (s.snapshot_id = ar.snapshot_id)
    WHERE s.instance_name = @instance_name
        AND s.snapshot_time_id = @snapshot_time_id
    GROUP BY ar.collection_time    

    ORDER BY collection_time
END;
GO

GRANT EXECUTE ON [snapshots].[rpt_sessions_and_connections]             TO [mdw_reader]
GO


--
-- snapshots.rpt_requests_and_compilations
--  Returns counts for number of requests/sec and related compilations counters
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @snapshot_time_id - time window identifier (snapshot ID from core.snapshots) 
--
IF (NOT OBJECT_ID(N'snapshots.rpt_requests_and_compilations', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[rpt_requests_and_compilations] ...'
    DROP PROCEDURE [snapshots].[rpt_requests_and_compilations]
END
GO 

PRINT 'Creating procedure [snapshots].[rpt_requests_and_compilations] ...'
GO
CREATE PROCEDURE [snapshots].[rpt_requests_and_compilations]
    @instance_name sysname,
    @snapshot_time_id int
AS
BEGIN
    SET NOCOUNT ON;

    SELECT 
        pc.performance_counter_name AS series,
        CONVERT (datetime, SWITCHOFFSET (CAST (pc.collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        pc.formatted_value
    FROM snapshots.performance_counters pc
    INNER JOIN core.snapshots s ON (s.snapshot_id = pc.snapshot_id)
    WHERE s.instance_name = @instance_name
        AND s.snapshot_time_id = @snapshot_time_id
        AND pc.performance_object_name LIKE '%SQL%:SQL Statistics'
        AND pc.performance_counter_name IN ('Batch Requests/sec', 'SQL Compilations/sec', 
            'SQL Re-Compilations/sec', 'Auto-Param Attempts/sec', 'Failed Auto-Params/sec')
    ORDER BY pc.collection_time, pc.performance_counter_name
END;
GO

GRANT EXECUTE ON [snapshots].[rpt_requests_and_compilations]            TO [mdw_reader]
GO

--
-- snapshots.rpt_plan_cache_hit_ratio
--  Returns details on plan cache hit ratio counter
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @snapshot_time_id - time window identifier (snapshot ID from core.snapshots) 
--
IF (NOT OBJECT_ID(N'snapshots.rpt_plan_cache_hit_ratio', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[rpt_plan_cache_hit_ratio] ...'
    DROP PROCEDURE [snapshots].[rpt_plan_cache_hit_ratio]
END
GO 

PRINT 'Creating procedure [snapshots].[rpt_plan_cache_hit_ratio] ...'
GO
CREATE PROCEDURE [snapshots].[rpt_plan_cache_hit_ratio]
    @instance_name sysname,
    @snapshot_time_id int
AS
BEGIN
    SET NOCOUNT ON;

    SELECT 
        ISNULL(pc.performance_instance_name, N'') AS series,
        CONVERT (datetime, SWITCHOFFSET (CAST (pc.collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        pc.formatted_value
    FROM snapshots.performance_counters pc
    INNER JOIN core.snapshots s ON (s.snapshot_id = pc.snapshot_id)
    WHERE s.instance_name = @instance_name
        AND s.snapshot_time_id = @snapshot_time_id
        AND pc.performance_object_name LIKE '%SQL%:Plan Cache'
        AND pc.performance_counter_name = 'Cache Hit Ratio'
        AND pc.performance_instance_name != '_Total'
    ORDER BY pc.collection_time, ISNULL(pc.performance_instance_name, N'')
END;
GO

GRANT EXECUTE ON [snapshots].[rpt_plan_cache_hit_ratio]                 TO [mdw_reader]
GO


--
-- snapshots.rpt_tempdb
--  Returns counters related to tempdb
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @snapshot_time_id - time window identifier (snapshot ID from core.snapshots) 
--
IF (NOT OBJECT_ID(N'snapshots.rpt_tempdb', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[rpt_tempdb] ...'
    DROP PROCEDURE [snapshots].[rpt_tempdb]
END
GO 

PRINT 'Creating procedure [snapshots].[rpt_tempdb] ...'
GO
CREATE PROCEDURE [snapshots].[rpt_tempdb]
    @instance_name sysname,
    @snapshot_time_id int
AS
BEGIN
    SET NOCOUNT ON;

    SELECT 
        pc.performance_counter_name AS series,
        CONVERT (datetime, SWITCHOFFSET (CAST (pc.collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        pc.formatted_value
    FROM snapshots.performance_counters pc
    INNER JOIN core.snapshots s ON (s.snapshot_id = pc.snapshot_id)
    WHERE s.instance_name = @instance_name
        AND s.snapshot_time_id = @snapshot_time_id
        AND (pc.performance_object_name LIKE '%SQL%:Transactions' OR pc.performance_object_name LIKE '%SQL%:General Statistics')
        AND pc.performance_counter_name IN ('Free Space in tempdb (KB)', 'Active Temp Tables')
    ORDER BY pc.collection_time, pc.performance_counter_name
END;
GO

GRANT EXECUTE ON [snapshots].[rpt_tempdb]                               TO [mdw_reader]
GO

--
-- snapshots.rpt_disk_speed_one_snapshot
--  Returns values for disk usage counters
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @snapshot_time_id - time window identifier (snapshot ID from core.snapshots) 
--
IF (NOT OBJECT_ID(N'snapshots.rpt_disk_speed_one_snapshot', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[rpt_disk_speed_one_snapshot] ...'
    DROP PROCEDURE [snapshots].[rpt_disk_speed_one_snapshot]
END
GO

PRINT 'Creating procedure [snapshots].[rpt_disk_speed_one_snapshot] ...'
GO
CREATE PROCEDURE [snapshots].[rpt_disk_speed_one_snapshot]
    @instance_name sysname,
    @snapshot_time_id int
AS
BEGIN
    SET NOCOUNT ON;
    
    SELECT 
        ISNULL(pc.performance_instance_name, N'') AS disk_letter,
        pc.performance_counter_name AS counter,
        CONVERT (datetime, SWITCHOFFSET (CAST (pc.collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        pc.formatted_value AS formatted_value
    FROM snapshots.performance_counters pc
    INNER JOIN core.snapshots s ON (s.snapshot_id = pc.snapshot_id)
    WHERE s.instance_name = @instance_name
        AND s.snapshot_time_id = @snapshot_time_id
        AND pc.performance_object_name = 'LogicalDisk' 
        AND pc.performance_counter_name IN ('Avg. Disk sec/Read', 'Avg. Disk sec/Write')
        AND pc.performance_instance_name != '_Total'
    ORDER BY 
        pc.collection_time, pc.performance_counter_name, ISNULL(pc.performance_instance_name, N'')
END;
GO

GRANT EXECUTE ON [snapshots].[rpt_disk_speed_one_snapshot]              TO [mdw_reader]
GO


--
-- snapshots.rpt_disk_queues_one_snapshot
--  Returns values for disk queue counters
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @snapshot_time_id - time window identifier (snapshot ID from core.snapshots) 
--
IF (NOT OBJECT_ID(N'snapshots.rpt_disk_queues_one_snapshot', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[rpt_disk_queues_one_snapshot] ...'
    DROP PROCEDURE [snapshots].[rpt_disk_queues_one_snapshot]
END
GO 

PRINT 'Creating procedure [snapshots].[rpt_disk_queues_one_snapshot] ...'
GO
CREATE PROCEDURE [snapshots].[rpt_disk_queues_one_snapshot]
    @instance_name sysname,
    @snapshot_time_id int
AS
BEGIN
    SET NOCOUNT ON;
    
    SELECT 
        ISNULL(pc.performance_instance_name, N'') AS disk_letter,
        pc.performance_counter_name AS counter,
        CONVERT (datetime, SWITCHOFFSET (CAST (pc.collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        pc.formatted_value
    FROM snapshots.performance_counters pc
    INNER JOIN core.snapshots s ON (s.snapshot_id = pc.snapshot_id)
    WHERE s.instance_name = @instance_name
        AND s.snapshot_time_id = @snapshot_time_id
        AND pc.performance_object_name = 'LogicalDisk' 
        AND pc.performance_counter_name IN ('Current Disk Queue Length', 'Avg. Disk Read Queue Length', 'Avg. Disk Write Queue Length')
        AND pc.performance_instance_name != '_Total'
    ORDER BY 
        pc.collection_time, pc.performance_counter_name, ISNULL(pc.performance_instance_name, N'')
END;
GO

GRANT EXECUTE ON [snapshots].[rpt_disk_queues_one_snapshot]             TO [mdw_reader]
GO


--
-- snapshots.rpt_disk_ratios_one_snapshot
--  Returns values for disk ratio counters
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @snapshot_time_id - time window identifier (snapshot ID from core.snapshots) 
--
IF (NOT OBJECT_ID(N'snapshots.rpt_disk_ratios_one_snapshot', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[rpt_disk_ratios_one_snapshot] ...'
    DROP PROCEDURE [snapshots].[rpt_disk_ratios_one_snapshot]
END
GO 

PRINT 'Creating procedure [snapshots].[rpt_disk_ratios_one_snapshot] ...'
GO
CREATE PROCEDURE [snapshots].[rpt_disk_ratios_one_snapshot]
    @instance_name sysname,
    @snapshot_time_id int
AS
BEGIN
    SET NOCOUNT ON;
    
    SELECT 
        ISNULL(pc.performance_instance_name, N'') AS disk_letter,
        pc.performance_counter_name AS counter,
        CONVERT (datetime, SWITCHOFFSET (CAST (pc.collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        pc.formatted_value
    FROM snapshots.performance_counters pc
    INNER JOIN core.snapshots s ON (s.snapshot_id = pc.snapshot_id)
    WHERE s.instance_name = @instance_name
        AND s.snapshot_time_id = @snapshot_time_id
        AND pc.performance_object_name = 'LogicalDisk' 
        AND pc.performance_counter_name IN ('Disk Reads/sec', 'Disk Writes/sec')
        AND pc.performance_instance_name != '_Total'
    ORDER BY 
        pc.collection_time, pc.performance_counter_name, ISNULL(pc.performance_instance_name, N'')
END;
GO

GRANT EXECUTE ON [snapshots].[rpt_disk_ratios_one_snapshot]             TO [mdw_reader]
GO


--
-- snapshots.rpt_disk_usage_per_process
--  Returns min, max, avg CPU usage and avg thread count for top 10 processes
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @snapshot_time_id - time window identifier (snapshot ID from core.snapshots) 
--
IF (NOT OBJECT_ID(N'snapshots.rpt_disk_usage_per_process', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[rpt_disk_usage_per_process] ...'
    DROP PROCEDURE [snapshots].[rpt_disk_usage_per_process]
END
GO 

PRINT 'Creating procedure [snapshots].[rpt_disk_usage_per_process] ...'
GO
CREATE PROCEDURE [snapshots].[rpt_disk_usage_per_process]
    @instance_name sysname,
    @snapshot_time_id int
AS
BEGIN
    SET NOCOUNT ON;
    
    SELECT TOP 10
        wb.process_name,
        wb.minimum_value AS wb_minimum_value,
        wb.maximum_value AS wb_maximum_value,
        wb.average_value AS wb_average_value,
        rb.minimum_value AS rb_minimum_value,
        rb.maximum_value AS rb_maximum_value,
        rb.average_value AS rb_average_value
    FROM
    (  SELECT
            ISNULL(pc.performance_instance_name, N'') AS process_name,
		    MIN(pc.formatted_value / (1024))		as minimum_value,
		    MAX(pc.formatted_value / (1024))		as maximum_value,
            AVG(pc.formatted_value / (1024))		as average_value
        FROM [snapshots].[performance_counters] as pc
        INNER JOIN core.snapshots s ON (s.snapshot_id = pc.snapshot_id)
        WHERE
            s.snapshot_time_id = @snapshot_time_id
            AND s.instance_name = @instance_name
            AND pc.performance_object_name = 'Process' 
            AND pc.performance_counter_name = 'IO Read Bytes/sec'
            AND pc.performance_instance_name NOT IN ('_Total', 'Idle')
        GROUP BY pc.performance_instance_name
    ) AS rb
    INNER JOIN
    (  SELECT
            ISNULL(pc.performance_instance_name, N'') AS process_name,
		    MIN(pc.formatted_value / (1024))		as minimum_value,
		    MAX(pc.formatted_value / (1024))		as maximum_value,
            AVG(pc.formatted_value / (1024))		as average_value
        FROM [snapshots].[performance_counters] as pc
        INNER JOIN core.snapshots s ON (s.snapshot_id = pc.snapshot_id)
        WHERE
            s.snapshot_time_id = @snapshot_time_id
            AND s.instance_name = @instance_name
            AND pc.performance_object_name = 'Process' 
            AND pc.performance_counter_name = 'IO Write Bytes/sec'
            AND pc.performance_instance_name NOT IN ('_Total', 'Idle')
        GROUP BY pc.performance_instance_name
    ) AS wb ON (wb.process_name = rb.process_name)
    ORDER BY wb_average_value DESC, rb_average_value DESC
END;
GO

GRANT EXECUTE ON [snapshots].[rpt_disk_usage_per_process]               TO [mdw_reader]
GO


-- snapshots.rpt_waiting_sessions_per_snapshot
--  Returns list of sessions waiting for specified wait type category
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @snapshot_time_id - time window identifier (snapshot ID from core.snapshots) 
--    @wait_category_name - Name of wait category to filter on 
--
IF (NOT OBJECT_ID(N'snapshots.rpt_waiting_sessions_per_snapshot', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[rpt_waiting_sessions_per_snapshot] ...'
    DROP PROCEDURE [snapshots].[rpt_waiting_sessions_per_snapshot]
END
GO 

PRINT 'Creating procedure [snapshots].[rpt_waiting_sessions_per_snapshot] ...'
GO
CREATE PROCEDURE [snapshots].[rpt_waiting_sessions_per_snapshot]
    @instance_name sysname,
    @snapshot_time_id int,
    @wait_category_name nvarchar(20)
AS
BEGIN
    SET NOCOUNT ON;
    
    DECLARE @total_waits TABLE
    (
        wait_type nvarchar(45),
        wait_count bigint
    )

    INSERT INTO @total_waits
    SELECT 
        ar.wait_type,
        COUNT(ar.wait_type) 
    FROM snapshots.active_sessions_and_requests ar
    JOIN core.snapshots s ON (s.snapshot_id = ar.snapshot_id)
    WHERE s.snapshot_time_id = @snapshot_time_id
      AND s.instance_name = @instance_name
    GROUP BY ar.wait_type
    
    SELECT
        t.source_id,
        t.session_id,
        t.request_id,
        t.database_name,
        CONVERT (datetime, SWITCHOFFSET (CAST (t.login_time AS datetimeoffset(7)), '+00:00')) AS login_time, 
        t.login_name,
        t.[program_name],
        t.sql_handle,
        master.dbo.fn_varbintohexstr (t.sql_handle) AS sql_handle_str,
        t.statement_start_offset,
        t.statement_end_offset,
        t.plan_handle,
        master.dbo.fn_varbintohexstr (t.plan_handle) AS plan_handle_str,
        t.wait_type,
        t.wait_count,
        t.wait_total_precent,
        t.wait_type_wait_precent,
        qt.query_text
    FROM
    (
        SELECT 
            s.source_id,
            ar.session_id,
            ar.request_id,
            ar.database_name,
            ar.login_time,
            ar.login_name,
            ar.program_name,
            ar.sql_handle,
            ar.statement_start_offset,
            ar.statement_end_offset,
            ar.plan_handle,
            ar.wait_type,
            COUNT(ar.wait_type) AS wait_count,
            (COUNT(ar.wait_type)) / CONVERT(decimal, (SELECT SUM(wait_count) FROM @total_waits)) AS wait_total_precent,
            (COUNT(ar.wait_type)) / CONVERT(decimal, (SELECT wait_count FROM @total_waits WHERE wait_type = ar.wait_type)) AS wait_type_wait_precent
        FROM snapshots.active_sessions_and_requests ar
        JOIN core.snapshots s ON (s.snapshot_id = ar.snapshot_id)
        JOIN core.wait_types ev on (ev.wait_type = ar.wait_type)
        JOIN core.wait_categories ct on (ct.category_id = ev.category_id)
        WHERE s.snapshot_time_id = @snapshot_time_id
          AND s.instance_name = @instance_name
          AND ct.category_name = @wait_category_name
        GROUP BY s.source_id, ar.session_id, ar.request_id, ar.database_name, ar.login_time, ar.login_name, ar.program_name, ar.wait_type, ar.sql_handle, ar.statement_start_offset, ar.statement_end_offset, ar.plan_handle
    ) t 
    OUTER APPLY snapshots.fn_get_query_text(t.source_id, t.sql_handle, t.statement_start_offset, t.statement_end_offset) AS qt

END;
GO
GRANT EXECUTE ON [snapshots].[rpt_waiting_sessions_per_snapshot]        TO [mdw_reader]
GO


--
-- snapshots.rpt_memory_usage_per_process
--  Returns min, max, avg CPU usage and avg thread count for top 10 processes
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @snapshot_time_id - time window identifier (snapshot ID from core.snapshots) 
--
IF (NOT OBJECT_ID(N'snapshots.rpt_memory_usage_per_process', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[rpt_memory_usage_per_process] ...'
    DROP PROCEDURE [snapshots].[rpt_memory_usage_per_process]
END
GO 

PRINT 'Creating procedure [snapshots].[rpt_memory_usage_per_process] ...'
GO
CREATE PROCEDURE [snapshots].[rpt_memory_usage_per_process]
    @instance_name sysname,
    @snapshot_time_id int
AS
BEGIN
    SET NOCOUNT ON;
    
    SELECT TOP 10
        ws.process_name,
        ws.minimum_value AS ws_minimum_value,
        ws.maximum_value AS ws_maximum_value,
        ws.average_value AS ws_average_value,
        vb.minimum_value AS vb_minimum_value,
        vb.maximum_value AS vb_maximum_value,
        vb.average_value AS vb_average_value
    FROM
    (  SELECT
            ISNULL(pc.performance_instance_name, N'') as process_name,
		    MIN(pc.formatted_value / (1024*1024))		as minimum_value,
		    MAX(pc.formatted_value / (1024*1024))		as maximum_value,
            AVG(pc.formatted_value / (1024*1024))		as average_value
        FROM [snapshots].[performance_counters] as pc
        INNER JOIN core.snapshots s ON (s.snapshot_id = pc.snapshot_id)
        WHERE
            s.snapshot_time_id = @snapshot_time_id
            AND s.instance_name = @instance_name
            AND pc.performance_object_name = 'Process' AND pc.performance_counter_name = 'Working Set'
            AND pc.performance_instance_name NOT IN ('_Total', 'Idle')
        GROUP BY pc.performance_instance_name
    ) AS ws
    INNER JOIN
    (  SELECT
            ISNULL(pc.performance_instance_name, N'') as process_name,
		    MIN(pc.formatted_value / (1024*1024))		as minimum_value,
		    MAX(pc.formatted_value / (1024*1024))		as maximum_value,
            AVG(pc.formatted_value / (1024*1024))		as average_value
        FROM [snapshots].[performance_counters] as pc
        INNER JOIN core.snapshots s ON (s.snapshot_id = pc.snapshot_id)
        WHERE
            s.snapshot_time_id = @snapshot_time_id
            AND s.instance_name = @instance_name
            AND pc.performance_object_name = 'Process' AND pc.performance_counter_name = 'Virtual Bytes'
            AND pc.performance_instance_name NOT IN ('_Total', 'Idle')
        GROUP BY pc.performance_instance_name
    ) AS vb ON (vb.process_name = ws.process_name)
    ORDER BY ws_average_value DESC

END;
GO

GRANT EXECUTE ON [snapshots].[rpt_memory_usage_per_process]             TO [mdw_reader]
GO


-- snapshots.rpt_wait_stats_by_category_by_snapshot
--  Returns deltas of wait stats with one snapshot interval and aggregated by category
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @start_time - (Optional) time window start (UTC)
--    @end_time - time window end (UTC)
--    @time_window_size - Number of intervals in the time window (provide if @start_time is NULL)
--    @time_interval_min - Number of minutes in each interval (provide if @start_time is NULL)
--    @category_name - (Optional) Name of wait category to filter on (all categories if NULL)
--
IF (NOT OBJECT_ID(N'snapshots.rpt_wait_stats_by_category_by_snapshot', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[rpt_wait_stats_by_category_by_snapshot] ...'
    DROP PROCEDURE [snapshots].[rpt_wait_stats_by_category_by_snapshot]
END
GO 

PRINT 'Creating procedure [snapshots].[rpt_wait_stats_by_category_by_snapshot] ...'
GO
CREATE PROCEDURE [snapshots].[rpt_wait_stats_by_category_by_snapshot]
    @instance_name sysname,
    @start_time datetime = NULL,
    @end_time datetime = NULL,
    @time_window_size smallint = NULL,
    @time_interval_min smallint = NULL, 
    @category_name nvarchar(20) = NULL
AS
BEGIN
    SET NOCOUNT ON;

    -- Convert snapshot_time (datetimeoffset) to a UTC datetime
    IF (@end_time IS NULL)
        SET @end_time = CONVERT (datetime, SWITCHOFFSET (CAST ((SELECT MAX(snapshot_time) FROM core.snapshots) AS datetimeoffset(7)), '+00:00'));

    IF (@start_time IS NULL)
    BEGIN
        -- If time_window_size and time_interval_min are set use them
        -- to determine the start time
        -- Otherwise use the earliest available snapshot_time
        IF @time_window_size IS NOT NULL AND @time_interval_min IS NOT NULL
        BEGIN
            SET @start_time = DATEADD(minute, @time_window_size * @time_interval_min * -1.0, @end_time);
        END
        ELSE
        BEGIN
            -- Convert min snapshot_time (datetimeoffset(7)) to a UTC datetime
            SET @start_time = CONVERT (datetime, SWITCHOFFSET (CAST ((SELECT MIN(snapshot_time) FROM core.snapshots) AS datetimeoffset(7)), '+00:00'));
        END
    END

    DECLARE @end_snapshot_time_id int;
    SELECT @end_snapshot_time_id = MAX(snapshot_time_id) FROM core.snapshots WHERE snapshot_time <= @end_time;

    DECLARE @start_snapshot_time_id int;
    SELECT @start_snapshot_time_id = MIN(snapshot_time_id) FROM core.snapshots WHERE snapshot_time >= @start_time;

    -- If the selected time window is > 1 hour, we'll chart wait times at 15 minute intervals.  If the selected 
    -- time window is < 1 hour, we'll use 5 minute intervals. 
    DECLARE @group_interval_min int
    IF DATEDIFF (minute, @start_time, @end_time) > 60
    BEGIN
        SET @group_interval_min = 15
    END 
    ELSE BEGIN
        SET @group_interval_min = 5
    END;
    -- Get wait times by waittype (plus CPU time, modeled as a waittype)
    WITH wait_times AS 
    ( 
        SELECT 
            s.snapshot_id, s.snapshot_time_id, s.snapshot_time, 
            DENSE_RANK() OVER (ORDER BY ws.collection_time) AS [rank], 
            ws.collection_time, wt.category_name, ws.wait_type, 
            ws.waiting_tasks_count, 
        ISNULL (ws.signal_wait_time_ms, 0) AS signal_wait_time_ms, 
        ISNULL (ws.wait_time_ms, 0) - ISNULL (ws.signal_wait_time_ms, 0) AS wait_time_ms, 
        ISNULL (ws.wait_time_ms, 0) AS wait_time_ms_cumulative 
    FROM snapshots.os_wait_stats AS ws
        INNER JOIN core.wait_types_categorized wt ON wt.wait_type = ws.wait_type
        INNER JOIN core.snapshots s ON ws.snapshot_id = s.snapshot_id
        WHERE s.snapshot_time_id BETWEEN @start_snapshot_time_id AND @end_snapshot_time_id
            AND s.instance_name = @instance_name
            AND wt.category_name = ISNULL (@category_name, wt.category_name)
        AND wt.ignore != 1

            AND ws.collection_time IN 
            (
                SELECT MAX(collection_time) 
                FROM snapshots.os_wait_stats ws2 
                WHERE ws2.collection_time BETWEEN @start_time AND @end_time
                GROUP BY DATEDIFF (minute, '19000101', ws2.collection_time) / @group_interval_min
            )
    )

    -- Get resource wait stats for this snapshot_time_id interval
    -- We must convert all datetimeoffset values to UTC datetime values before returning to Reporting Services
    SELECT 
        CONVERT (datetime, SWITCHOFFSET (CAST (w2.collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        CONVERT (datetime, SWITCHOFFSET (CAST (w2.snapshot_time AS datetimeoffset(7)), '+00:00')) AS snapshot_time, 
        w2.snapshot_time_id, 
        CONVERT (datetime, SWITCHOFFSET (CAST (w1.collection_time AS datetimeoffset(7)), '+00:00')) AS interval_start, 
        CONVERT (datetime, SWITCHOFFSET (CAST (w2.collection_time AS datetimeoffset(7)), '+00:00')) AS interval_end, 
        w2.category_name, w2.wait_type, 
        -- All wait stats will be reset to zero by a service cycle, which will cause 
        -- (snapshot2waittime-snapshot1waittime) calculations to produce an incorrect 
        -- negative wait time for the interval.  Detect this and avoid calculating 
        -- negative wait time/wait count/signal time deltas. 
        CASE 
            WHEN (w2.waiting_tasks_count - w1.waiting_tasks_count) < 0 THEN w2.waiting_tasks_count 
            ELSE (w2.waiting_tasks_count - w1.waiting_tasks_count) 
        END AS waiting_tasks_count_delta, 
        CASE 
            WHEN (w2.wait_time_ms - w1.wait_time_ms) < 0 THEN w2.wait_time_ms
            ELSE (w2.wait_time_ms - w1.wait_time_ms)
        END / CAST (DATEDIFF (second, w1.collection_time, w2.collection_time) AS decimal) AS resource_wait_time_delta, 
        CASE 
            WHEN (w2.signal_wait_time_ms - w1.signal_wait_time_ms) < 0 THEN w2.signal_wait_time_ms 
            ELSE (w2.signal_wait_time_ms - w1.signal_wait_time_ms) 
        END / CAST (DATEDIFF (second, w1.collection_time, w2.collection_time) AS decimal) AS resource_signal_time_delta, 
        DATEDIFF (second, w1.collection_time, w2.collection_time) AS interval_sec, 
        w2.wait_time_ms_cumulative
    -- Self-join - w1 represents wait stats at the beginning of the sample interval, while w2 
    -- shows the wait stats at the end of the interval. 
    FROM wait_times AS w1 
    INNER JOIN wait_times AS w2 ON w1.wait_type = w2.wait_type AND w1.[rank] = w2.[rank]-1 

    UNION ALL 

    -- Treat sum of all signal waits as CPU "wait time"
    SELECT 
        CONVERT (datetime, SWITCHOFFSET (CAST (w2.collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        CONVERT (datetime, SWITCHOFFSET (CAST (w2.snapshot_time AS datetimeoffset(7)), '+00:00')) AS snapshot_time, 
        w2.snapshot_time_id, 
        CONVERT (datetime, SWITCHOFFSET (CAST (w1.collection_time AS datetimeoffset(7)), '+00:00')) AS interval_start, 
        CONVERT (datetime, SWITCHOFFSET (CAST (w2.collection_time AS datetimeoffset(7)), '+00:00')) AS interval_end, 
        'CPU' AS category_name, 
        'CPU (Signal Wait)' AS wait_type, 
        0 AS waiting_tasks_count_delta, 
        -- Handle wait stats resets
        CASE 
            WHEN (SUM (w2.signal_wait_time_ms) - SUM (w1.signal_wait_time_ms)) < 0 THEN SUM (w2.signal_wait_time_ms)
            ELSE (SUM (w2.signal_wait_time_ms) - SUM (w1.signal_wait_time_ms)) 
        END / CAST (DATEDIFF (second, w1.collection_time, w2.collection_time) AS decimal) AS resource_wait_time_delta, 
        0 AS resource_signal_time_delta, 
        DATEDIFF (second, w1.collection_time, w2.collection_time) AS interval_sec, 
        w2.wait_time_ms_cumulative
    FROM wait_times AS w1
    INNER JOIN wait_times AS w2 ON w1.wait_type = w2.wait_type AND w1.[rank] = w2.[rank]-1
    -- Only return CPU stats if we were told to return the 'CPU' category or all categories
    WHERE (@category_name IS NULL OR @category_name = 'CPU')
    GROUP BY w1.collection_time, w1.collection_time, w2.collection_time, w2.snapshot_time, w2.snapshot_time_id, w2.wait_time_ms_cumulative

    UNION ALL 

    -- Get actual used CPU time from perfmon data (average across the whole snapshot_time_id interval, 
    -- and use this average for each sample time in this interval).  Note that the "% Processor Time" 
    -- counter in the "Process" object can exceed 100% (for example, it can range from 0-800% on an 
    -- 8 CPU server). 
    SELECT
        CONVERT (datetime, SWITCHOFFSET (CAST (w2.collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        CONVERT (datetime, SWITCHOFFSET (CAST (w2.snapshot_time AS datetimeoffset(7)), '+00:00')) AS snapshot_time, 
        w2.snapshot_time_id, 
        CONVERT (datetime, SWITCHOFFSET (CAST (w1.collection_time AS datetimeoffset(7)), '+00:00')) AS interval_start, 
        CONVERT (datetime, SWITCHOFFSET (CAST (w2.collection_time AS datetimeoffset(7)), '+00:00')) AS interval_end, 
        'CPU' AS category_name, 
        'CPU (Consumed)' AS wait_type, 
        0 AS waiting_tasks_count_delta, 
        -- Get sqlservr %CPU usage for the perfmon sample that immediately precedes 
        -- each wait stats sample.  Multiply by 10 to convert "% CPU" to "ms CPU/sec". 
        10 * (  
            SELECT TOP 1 formatted_value
            FROM snapshots.performance_counters AS pc
            INNER JOIN core.snapshots s ON pc.snapshot_id = s.snapshot_id
            WHERE pc.performance_object_name = 'Process' AND pc.performance_counter_name = '% Processor Time' 
                AND pc.performance_instance_name = 'sqlservr'
                AND s.snapshot_time_id <= @end_snapshot_time_id
                AND s.instance_name = @instance_name
                AND pc.snapshot_id < w2.snapshot_id
            ORDER BY pc.snapshot_id DESC
        ) AS resource_wait_time_delta, 
        0 AS resource_signal_time_delta, 
        DATEDIFF (second, w1.collection_time, w2.collection_time) AS interval_sec, 
        NULL
    FROM wait_times AS w1
    INNER JOIN wait_times AS w2 ON w1.wait_type = w2.wait_type AND w1.[rank] = w2.[rank]-1
    -- Only return CPU stats if we weren't passed in a specific wait category
    WHERE (@category_name IS NULL OR @category_name = 'CPU')
    GROUP BY w1.collection_time, w2.collection_time, w2.snapshot_time, w2.snapshot_time_id, w2.snapshot_id

    ORDER BY collection_time, category_name, wait_type
    -- These trace flags are necessary for a good plan, due to the join on ascending core.snapshot PK
    OPTION (QUERYTRACEON 2389, QUERYTRACEON 2390);
END
GO

GRANT EXECUTE ON [snapshots].[rpt_wait_stats_by_category_by_snapshot]   TO [mdw_reader]
GO


--
-- snapshots.rpt_cpu_usage
--  Returns values for System CPU usage and SQL Server CPU usage collected through perf counters.
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @start_time - (Optional) time window start (UTC)
--    @end_time - time window end (UTC)
--    @time_window_size - Number of intervals in the time window (provide if @start_time is NULL)
--    @time_interval_min - Number of minutes in each interval (provide if @start_time is NULL)
--
IF (NOT OBJECT_ID(N'snapshots.rpt_cpu_usage', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[rpt_cpu_usage] ...'
    DROP PROCEDURE [snapshots].[rpt_cpu_usage]
END
GO 

PRINT 'Creating procedure [snapshots].[rpt_cpu_usage] ...'
GO
CREATE PROCEDURE [snapshots].[rpt_cpu_usage]
    @instance_name sysname,
    @start_time datetime = NULL,
    @end_time datetime = NULL,
    @time_window_size smallint = NULL,
    @time_interval_min smallint = NULL
AS
BEGIN
    SET NOCOUNT ON;

    -- Convert snapshot_time (datetimeoffset(7)) to a UTC datetime
    IF (@end_time IS NULL)
        SET @end_time = CONVERT (datetime, SWITCHOFFSET (CAST ((SELECT MAX(snapshot_time) FROM core.snapshots) AS datetimeoffset(7)), '+00:00'));

    IF (@start_time IS NULL)
    BEGIN
        -- If time_window_size and time_interval_min are set use them
        -- to determine the start time
        -- Otherwise use the earliest available snapshot_time
        IF @time_window_size IS NOT NULL AND @time_interval_min IS NOT NULL
        BEGIN
            SET @start_time = DATEADD(minute, @time_window_size * @time_interval_min * -1.0, @end_time);
        END
        ELSE
        BEGIN
            -- Convert min snapshot_time (datetimeoffset(7)) to a UTC datetime
            SET @start_time = CONVERT (datetime, SWITCHOFFSET (CAST ((SELECT MIN(snapshot_time) FROM core.snapshots) AS datetimeoffset(7)), '+00:00'));
        END
    END

    DECLARE @end_snapshot_time_id int;
    SELECT @end_snapshot_time_id = MAX(snapshot_time_id) FROM core.snapshots WHERE snapshot_time <= @end_time;

    DECLARE @start_snapshot_time_id int;
    SELECT @start_snapshot_time_id = MIN(snapshot_time_id) FROM core.snapshots WHERE snapshot_time >= @start_time;

    -- Determine the CPU count on the target system by querying the number of "Processor" 
    -- counter instances we captured in a perfmon sample that was captured around the same 
    -- time. 
    DECLARE @cpu_count smallint
        SELECT @cpu_count = COUNT (DISTINCT pc.performance_instance_name)
        FROM snapshots.performance_counters AS pc
        INNER JOIN core.snapshots s ON s.snapshot_id = pc.snapshot_id 
        WHERE pc.performance_object_name = 'Processor' AND pc.performance_counter_name = '% Processor Time'
        AND pc.performance_instance_name != '_Total'
        AND s.snapshot_time_id BETWEEN @start_snapshot_time_id AND @end_snapshot_time_id 
        AND s.instance_name = @instance_name
            AND pc.collection_time = 
                (SELECT TOP 1 collection_time FROM snapshots.performance_counter_values pcv2 WHERE pcv2.snapshot_id = s.snapshot_id)
    SELECT 
        CASE pc.performance_object_name
            WHEN 'Processor' THEN 'System'
            ELSE 'SQL Server'
        END AS series,
        s.snapshot_time_id, 
        CONVERT (datetime, SWITCHOFFSET (CAST (s.snapshot_time AS datetimeoffset(7)), '+00:00')) AS snapshot_time, 
        -- Processor time on an eight proc system is 0-800% for the Process object, 
        -- but 0-100% for the Processor object
        CASE pc.performance_object_name
            WHEN 'Processor' THEN pc.formatted_value 
            ELSE pc.formatted_value / @cpu_count
        END AS formatted_value
    FROM snapshots.performance_counters pc
    JOIN core.snapshots s ON (s.snapshot_id = pc.snapshot_id)
    WHERE s.instance_name = @instance_name
        AND s.snapshot_time_id BETWEEN @start_snapshot_time_id AND @end_snapshot_time_id
        AND 
        (
            (pc.performance_object_name = 'Processor' AND pc.performance_counter_name = '% Processor Time' AND pc.performance_instance_name = '_Total')
            OR (pc.performance_object_name = 'Process' AND pc.performance_counter_name = '% Processor Time' AND pc.performance_instance_name = 'sqlservr')
        )
    ORDER BY pc.collection_time, series
        -- These trace flags are necessary for a good plan, due to the join on ascending PK w/range filter
        OPTION (QUERYTRACEON 2389, QUERYTRACEON 2390)
        
END;
GO

GRANT EXECUTE ON [snapshots].[rpt_cpu_usage]                            TO [mdw_reader]
GO


--
-- snapshots.rpt_mem_usage
--  Returns values for System memory usage and SQL Server memory usage collected through perf counters.
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @start_time - (Optional) time window start (UTC)
--    @end_time - time window end (UTC)
--    @time_window_size - Number of intervals in the time window (provide if @start_time is NULL)
--    @time_interval_min - Number of minutes in each interval (provide if @start_time is NULL)
--
IF (NOT OBJECT_ID(N'snapshots.rpt_mem_usage', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[rpt_mem_usage] ...'
    DROP PROCEDURE [snapshots].[rpt_mem_usage]
END
GO 

PRINT 'Creating procedure [snapshots].[rpt_mem_usage] ...'
GO
CREATE PROCEDURE [snapshots].[rpt_mem_usage]
    @instance_name sysname,
    @start_time datetime = NULL,
    @end_time datetime = NULL,
    @time_window_size smallint = NULL,
    @time_interval_min smallint = NULL
AS
BEGIN
    SET NOCOUNT ON;

    -- Convert snapshot_time (datetimeoffset) to a UTC datetime
    IF (@end_time IS NULL)
        SET @end_time = CONVERT (datetime, SWITCHOFFSET (CAST ((SELECT MAX(snapshot_time) FROM core.snapshots) AS datetimeoffset(7)), '+00:00'));

    IF (@start_time IS NULL)
    BEGIN
        -- If time_window_size and time_interval_min are set use them
        -- to determine the start time
        -- Otherwise use the earliest available snapshot_time
        IF @time_window_size IS NOT NULL AND @time_interval_min IS NOT NULL
        BEGIN
            SET @start_time = DATEADD(minute, @time_window_size * @time_interval_min * -1.0, @end_time);
        END
        ELSE
        BEGIN
            -- Convert min snapshot_time (datetimeoffset) to a UTC datetime
            SET @start_time = CONVERT (datetime, SWITCHOFFSET (CAST ((SELECT MIN(snapshot_time) FROM core.snapshots) AS datetimeoffset(7)), '+00:00'));
        END
    END

    DECLARE @end_snapshot_time_id int;
    SELECT @end_snapshot_time_id = MAX(snapshot_time_id) FROM core.snapshots WHERE snapshot_time <= @end_time;

    DECLARE @start_snapshot_time_id int;
    SELECT @start_snapshot_time_id = MIN(snapshot_time_id) FROM core.snapshots WHERE snapshot_time >= @start_time;

    SELECT 
        N'System' AS series,
        s.snapshot_time_id,
        CONVERT (datetime, SWITCHOFFSET (CAST (s.snapshot_time AS datetimeoffset(7)), '+00:00')) AS snapshot_time, 
        AVG(pc.formatted_value/(1024*1024)) AS formatted_value
    FROM snapshots.performance_counters pc
    JOIN core.snapshots s ON (s.snapshot_id = pc.snapshot_id)
    WHERE s.instance_name = @instance_name
        AND s.snapshot_time_id BETWEEN @start_snapshot_time_id AND @end_snapshot_time_id
        AND (pc.performance_object_name = 'Process' AND pc.performance_counter_name = 'Working Set' AND pc.performance_instance_name = '_Total')
             -- uncomment when defect 109313 is fixed
             --OR pc.path LIKE N'\Memory\Pool Nonpaged Bytes' 
             --OR pc.path LIKE N'\Memory\Cache Bytes')
    GROUP BY
        s.snapshot_time_id,
        s.snapshot_time
    UNION ALL 
    SELECT 
        N'SQL Server' AS series,
        s.snapshot_time_id,
        CONVERT (datetime, SWITCHOFFSET (CAST (s.snapshot_time AS datetimeoffset(7)), '+00:00')) AS snapshot_time, 
        AVG(pc.formatted_value/(1024*1024)) AS formatted_value
    FROM snapshots.performance_counters pc
    JOIN core.snapshots s ON (s.snapshot_id = pc.snapshot_id)
    WHERE s.instance_name = @instance_name
        AND s.snapshot_time_id BETWEEN @start_snapshot_time_id AND @end_snapshot_time_id
        AND (pc.performance_object_name = 'Process' AND pc.performance_counter_name = 'Working Set' AND pc.performance_instance_name = 'sqlservr')
    GROUP BY
        s.snapshot_time_id,
        s.snapshot_time
    ORDER BY 
        snapshot_time_id, series
    -- These trace flags are necessary for a good plan, due to the join on ascending PK w/range filter
    OPTION (QUERYTRACEON 2389, QUERYTRACEON 2390)
END;
GO

GRANT EXECUTE ON [snapshots].[rpt_mem_usage]                            TO [mdw_reader]
GO

--
-- snapshots.rpt_io_usage
--  Returns values for System Disk IO usage and SQL Server Disk IO usage collected through perf counters.
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @start_time - (Optional) time window start (UTC)
--    @end_time - time window end (UTC)
--    @time_window_size - Number of intervals in the time window (provide if @start_time is NULL)
--    @time_interval_min - Number of minutes in each interval (provide if @start_time is NULL)
--
IF (NOT OBJECT_ID(N'snapshots.rpt_io_usage', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[rpt_io_usage] ...'
    DROP PROCEDURE [snapshots].[rpt_io_usage]
END
GO 

PRINT 'Creating procedure [snapshots].[rpt_io_usage] ...'
GO
CREATE PROCEDURE [snapshots].[rpt_io_usage]
    @instance_name sysname,
    @start_time datetime = NULL,
    @end_time datetime = NULL,
    @time_window_size smallint = NULL,
    @time_interval_min smallint = NULL
AS
BEGIN
    SET NOCOUNT ON;

    -- Convert snapshot_time (datetimeoffset) to a UTC datetime
    IF (@end_time IS NULL)
        SET @end_time = CONVERT (datetime, SWITCHOFFSET (CAST ((SELECT MAX(snapshot_time) FROM core.snapshots) AS datetimeoffset(7)), '+00:00'));

    IF (@start_time IS NULL)
    BEGIN
        -- If time_window_size and time_interval_min are set use them
        -- to determine the start time
        -- Otherwise use the earliest available snapshot_time
        IF @time_window_size IS NOT NULL AND @time_interval_min IS NOT NULL
        BEGIN
            SET @start_time = DATEADD(minute, @time_window_size * @time_interval_min * -1.0, @end_time);
        END
        ELSE
        BEGIN
            -- Convert min snapshot_time (datetimeoffset) to a UTC datetime
            SET @start_time = CONVERT (datetime, SWITCHOFFSET (CAST ((SELECT MIN(snapshot_time) FROM core.snapshots) AS datetimeoffset(7)), '+00:00'));
        END
    END

    DECLARE @end_snapshot_time_id int;
    SELECT @end_snapshot_time_id = MAX(snapshot_time_id) FROM core.snapshots WHERE snapshot_time <= @end_time;

    DECLARE @start_snapshot_time_id int;
    SELECT @start_snapshot_time_id = MIN(snapshot_time_id) FROM core.snapshots WHERE snapshot_time >= @start_time;

    SELECT 
        N'System' AS series,
        s.snapshot_time_id,
        CONVERT (datetime, SWITCHOFFSET (CAST (s.snapshot_time AS datetimeoffset(7)), '+00:00')) AS snapshot_time, 
        AVG(pc.formatted_value/(1024)) AS formatted_value
    FROM snapshots.performance_counters pc
    JOIN core.snapshots s ON (s.snapshot_id = pc.snapshot_id)
    WHERE s.instance_name = @instance_name
        AND s.snapshot_time_id BETWEEN @start_snapshot_time_id AND @end_snapshot_time_id
        AND (pc.performance_object_name = 'Process' AND pc.performance_counter_name = 'IO Read Bytes/sec' AND pc.performance_instance_name = '_Total')
             -- uncomment when defect 109313 is fixed
             --OR pc.path LIKE N'\Process(!_Total)\IO Write Bytes/sec' ESCAPE N'!')
    GROUP BY
        s.snapshot_time_id,
        s.snapshot_time
    UNION ALL 
    SELECT 
        N'SQL Server' AS series,
        s.snapshot_time_id,
        CONVERT (datetime, SWITCHOFFSET (CAST (s.snapshot_time AS datetimeoffset(7)), '+00:00')) AS snapshot_time, 
        AVG(pc.formatted_value/(1024)) AS formatted_value
    FROM snapshots.performance_counters pc
    JOIN core.snapshots s ON (s.snapshot_id = pc.snapshot_id)
    WHERE s.instance_name = @instance_name
        AND s.snapshot_time_id BETWEEN @start_snapshot_time_id AND @end_snapshot_time_id
        AND (pc.performance_object_name = 'Process' AND pc.performance_counter_name = 'IO Read Bytes/sec' AND pc.performance_instance_name = 'sqlservr')
             -- uncomment when defect 109313 is fixed
             --OR pc.path LIKE N'\Process(sqlservr)\IO Write Bytes/sec')
    GROUP BY
        s.snapshot_time_id,
        s.snapshot_time
    ORDER BY 
        snapshot_time_id, series
    -- These trace flags are necessary for a good plan, due to the join on ascending PK w/range filter
    OPTION (QUERYTRACEON 2389, QUERYTRACEON 2390)
END;
GO

GRANT EXECUTE ON [snapshots].[rpt_io_usage]                             TO [mdw_reader]
GO


--
-- snapshots.rpt_network_usage
--  Returns values for System network usage and SQL Server network usage collected through perf counters.
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @start_time - (Optional) time window start (UTC)
--    @end_time - time window end (UTC)
--    @time_window_size - Number of intervals in the time window (provide if @start_time is NULL)
--    @time_interval_min - Number of minutes in each interval (provide if @start_time is NULL)
--
IF (NOT OBJECT_ID(N'snapshots.rpt_network_usage', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[rpt_network_usage] ...'
    DROP PROCEDURE [snapshots].[rpt_network_usage]
END
GO 

PRINT 'Creating procedure [snapshots].[rpt_network_usage] ...'
GO
CREATE PROCEDURE [snapshots].[rpt_network_usage]
    @instance_name sysname,
    @start_time datetime = NULL,
    @end_time datetime = NULL,
    @time_window_size smallint = NULL,
    @time_interval_min smallint = NULL
AS
BEGIN
    SET NOCOUNT ON;

    -- Convert snapshot_time (datetimeoffset) to a UTC datetime
    IF (@end_time IS NULL)
        SET @end_time = CONVERT (datetime, SWITCHOFFSET (CAST ((SELECT MAX(snapshot_time) FROM core.snapshots) AS datetimeoffset(7)), '+00:00'));

    IF (@start_time IS NULL)
    BEGIN
        -- If time_window_size and time_interval_min are set use them
        -- to determine the start time
        -- Otherwise use the earliest available snapshot_time
        IF @time_window_size IS NOT NULL AND @time_interval_min IS NOT NULL
        BEGIN
            SET @start_time = DATEADD(minute, @time_window_size * @time_interval_min * -1.0, @end_time);
        END
        ELSE
        BEGIN
            -- Convert min snapshot_time (datetimeoffset) to a UTC datetime
            SET @start_time = CONVERT (datetime, SWITCHOFFSET (CAST ((SELECT MIN(snapshot_time) FROM core.snapshots) AS datetimeoffset(7)), '+00:00'));
        END
    END

    DECLARE @end_snapshot_time_id int;
    SELECT @end_snapshot_time_id = MAX(snapshot_time_id) FROM core.snapshots WHERE snapshot_time <= @end_time;

    DECLARE @start_snapshot_time_id int;
    SELECT @start_snapshot_time_id = MIN(snapshot_time_id) FROM core.snapshots WHERE snapshot_time >= @start_time;

    SELECT 
        N'System' AS series,
        s.snapshot_time_id,
        CONVERT (datetime, SWITCHOFFSET (CAST (s.snapshot_time AS datetimeoffset(7)), '+00:00')) AS snapshot_time, 
        AVG(pc.formatted_value/(1024)) AS formatted_value
    FROM snapshots.performance_counters pc
    JOIN core.snapshots s ON (s.snapshot_id = pc.snapshot_id)
    WHERE s.instance_name = @instance_name
        AND s.snapshot_time_id BETWEEN @start_snapshot_time_id AND @end_snapshot_time_id
        -- all instances 
        AND (pc.performance_object_name = 'Network Interface' AND pc.performance_counter_name = 'Bytes Total/sec')
    GROUP BY
        s.snapshot_time_id,
        s.snapshot_time
    ORDER BY 
        snapshot_time_id
    -- These trace flags are necessary for a good plan, due to the join on ascending PK w/range filter
    OPTION (QUERYTRACEON 2389, QUERYTRACEON 2390)
END;
GO

GRANT EXECUTE ON [snapshots].[rpt_network_usage]                        TO [mdw_reader]
GO


--
-- snapshots.rpt_wait_stats_one_snapshot
--  Returns summary of wait stats grouped per wait category for one snapshot
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @snapshot_time_id - time window identifier (snapshot ID from core.snapshots) 
--    @category_name - (Optional) Name of wait category to filter on (all categories if NULL)
--
IF (NOT OBJECT_ID(N'snapshots.rpt_wait_stats_one_snapshot', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[rpt_wait_stats_one_snapshot] ...'
    DROP PROCEDURE [snapshots].[rpt_wait_stats_one_snapshot]
END
GO 

PRINT 'Creating procedure [snapshots].[rpt_wait_stats_one_snapshot] ...'
GO
CREATE PROCEDURE [snapshots].[rpt_wait_stats_one_snapshot]
    @instance_name sysname,
    @snapshot_time_id int, 
    @category_name nvarchar(20) = NULL
AS
BEGIN
    SET NOCOUNT ON;

    -- Find the snapshot_id of the last wait stats data set in the following 
    -- snapshot_time_id interval
    DECLARE @current_snapshot_id int
    SELECT TOP 1 @current_snapshot_id = s.snapshot_id
    FROM core.snapshots AS s
    INNER JOIN snapshots.os_wait_stats ws ON s.snapshot_id = ws.snapshot_id
    WHERE s.instance_name = @instance_name
        AND s.snapshot_time_id > @snapshot_time_id
    ORDER BY s.snapshot_time_id ASC, s.snapshot_id ASC, ws.collection_time ASC;

    -- Find the snapshot_id of the last wait stats data set in the preceding 
    -- snapshot_time_id interval
    DECLARE @previous_snapshot_id int
    SELECT TOP 1 @previous_snapshot_id = ISNULL (s.snapshot_id, 0)
    FROM core.snapshots AS s
    INNER JOIN snapshots.os_wait_stats ws ON s.snapshot_id = ws.snapshot_id
    WHERE s.instance_name = @instance_name
        AND s.snapshot_time_id < @snapshot_time_id
    ORDER BY s.snapshot_time_id DESC, s.snapshot_id DESC, ws.collection_time DESC;

    -- Get wait times by waittype (plus CPU time, modeled as a waittype)
    WITH wait_times AS 
    ( 
        SELECT 
            s.snapshot_id, s.snapshot_time_id, 
            DENSE_RANK() OVER (ORDER BY collection_time) AS [rank], 
            ws.collection_time, wt.category_name, ws.wait_type, 
            ws.waiting_tasks_count, 
            ISNULL (ws.signal_wait_time_ms, 0) AS signal_wait_time_ms, 
            ISNULL (ws.wait_time_ms - ISNULL (ws.signal_wait_time_ms, 0), 0) AS wait_time_ms, 
            ws.wait_time_ms AS wait_time_ms_cumulative
        FROM snapshots.os_wait_stats AS ws
        INNER JOIN core.wait_types_categorized wt ON wt.wait_type = ws.wait_type
        INNER JOIN core.snapshots s ON ws.snapshot_id = s.snapshot_id
        WHERE s.snapshot_id BETWEEN @previous_snapshot_id AND @current_snapshot_id
            AND s.instance_name = @instance_name
            AND (wt.category_name = ISNULL (@category_name, wt.category_name))
            AND wt.ignore != 1
    )

    -- Get resource wait stats for this snapshot_time_id interval
    SELECT 
        CONVERT (datetime, SWITCHOFFSET (CAST (w2.collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        w2.category_name, w2.wait_type, 
        -- All wait stats will be reset to zero by a service cycle, which will cause 
        -- (snapshot2waittime-snapshot1waittime) calculations to produce an incorrect 
        -- negative wait time for the interval.  Detect this and avoid calculating 
        -- negative wait time/wait count/signal time deltas. 
        CASE 
            WHEN (w2.waiting_tasks_count - w1.waiting_tasks_count) < 0 THEN w2.waiting_tasks_count 
            ELSE (w2.waiting_tasks_count - w1.waiting_tasks_count) 
        END AS waiting_tasks_count_delta, 
        CASE 
            WHEN (w2.wait_time_ms - w1.wait_time_ms) < 0 THEN w2.wait_time_ms
            ELSE (w2.wait_time_ms - w1.wait_time_ms)
        END / CAST (DATEDIFF (second, w1.collection_time, w2.collection_time) AS decimal) AS resource_wait_time_delta, 
        CASE 
            WHEN (w2.signal_wait_time_ms - w1.signal_wait_time_ms) < 0 THEN w2.signal_wait_time_ms 
            ELSE (w2.signal_wait_time_ms - w1.signal_wait_time_ms) 
        END / CAST (DATEDIFF (second, w1.collection_time, w2.collection_time) AS decimal) AS resource_signal_time_delta, 
        DATEDIFF (second, w1.collection_time, w2.collection_time) AS interval_sec, 
        w2.wait_time_ms_cumulative
    -- Self-join - w1 represents wait stats at the beginning of the sample interval, while w2 
    -- shows the wait stats at the end of the interval. 
    FROM wait_times AS w1 
    INNER JOIN wait_times AS w2 ON w1.wait_type = w2.wait_type AND w1.[rank] = w2.[rank]-1 

    UNION ALL 

    -- Treat sum of all signal waits as CPU "wait time"
    SELECT 
        CONVERT (datetime, SWITCHOFFSET (CAST (w2.collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        'CPU' AS category_name, 
        'CPU (Signal Wait)' AS wait_type, 
        0 AS waiting_tasks_count_delta, 
        -- Handle wait stats resets
        CASE 
            WHEN (SUM (w2.signal_wait_time_ms) - SUM (w1.signal_wait_time_ms)) < 0 THEN SUM (w2.signal_wait_time_ms)
            ELSE (SUM (w2.signal_wait_time_ms) - SUM (w1.signal_wait_time_ms)) 
        END / CAST (DATEDIFF (second, w1.collection_time, w2.collection_time) AS decimal) AS resource_wait_time_delta, 
        0 AS resource_signal_time_delta, 
        DATEDIFF (second, w1.collection_time, w2.collection_time) AS interval_sec, 
        w2.wait_time_ms_cumulative
    FROM wait_times AS w1
    INNER JOIN wait_times AS w2 ON w1.wait_type = w2.wait_type AND w1.[rank] = w2.[rank]-1
    -- Only return CPU stats if we weren't passed in a specific wait category
    WHERE (@category_name IS NULL OR @category_name = 'CPU')
    GROUP BY w1.collection_time, w2.collection_time, w2.wait_time_ms_cumulative

    UNION ALL 

    -- Get actual used CPU time from perfmon data (average across the whole snapshot_time_id interval, 
    -- and use this average for each sample time in this interval).  Note that the "% Processor Time" 
    -- counter in the "Process" object can exceed 100% (for example, it can range from 0-800% on an 
    -- 8 CPU server). 
    SELECT
        CONVERT (datetime, SWITCHOFFSET (CAST (w2.collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        'CPU' AS category_name, 
        'CPU (Consumed)' AS wait_type, 
        0 AS waiting_tasks_count_delta, 
        -- Get sqlservr %CPU usage for the perfmon sample that immediately precedes 
        -- each wait stats sample.  Multiply by 10 to convert "% CPU" to "ms CPU/sec". 
        10 * (  
            SELECT TOP 1 formatted_value
            FROM snapshots.performance_counters AS pc
            INNER JOIN core.snapshots s ON pc.snapshot_id = s.snapshot_id
            WHERE pc.performance_object_name = 'Process' AND pc.performance_counter_name = '% Processor Time' 
                AND pc.performance_instance_name = 'sqlservr'
                AND s.snapshot_id < @current_snapshot_id
                AND s.instance_name = @instance_name
                AND pc.snapshot_id < w2.snapshot_id
            ORDER BY pc.snapshot_id DESC
        ) AS resource_wait_time_delta, 
        0 AS resource_signal_time_delta, 
        DATEDIFF (second, w1.collection_time, w2.collection_time) AS interval_sec, 
        NULL
    FROM wait_times AS w1
    INNER JOIN wait_times AS w2 ON w1.wait_type = w2.wait_type AND w1.[rank] = w2.[rank]-1
    -- Only return CPU stats if we weren't passed in a specific wait category
    WHERE (@category_name IS NULL OR @category_name = 'CPU')
    GROUP BY w1.collection_time, w2.collection_time, w2.snapshot_id

    ORDER BY collection_time, category_name, wait_type
    -- These trace flags are necessary for a good plan, due to the join on ascending core.snapshot PK
    OPTION (QUERYTRACEON 2389, QUERYTRACEON 2390);

END;
GO

GRANT EXECUTE ON [snapshots].[rpt_wait_stats_one_snapshot]              TO [mdw_reader]
GO

--
-- snapshots.rpt_server_activity
--  Returns values for various perf counters indicating server's activity
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @start_time - (Optional) time window start (UTC)
--    @end_time - time window end (UTC)
--    @time_window_size - Number of intervals in the time window (provide if @start_time is NULL)
--    @time_interval_min - Number of minutes in each interval (provide if @start_time is NULL)
--
IF (NOT OBJECT_ID(N'snapshots.rpt_server_activity', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[rpt_server_activity] ...'
    DROP PROCEDURE [snapshots].[rpt_server_activity]
END
GO 

PRINT 'Creating procedure [snapshots].[rpt_server_activity] ...'
GO
CREATE PROCEDURE [snapshots].[rpt_server_activity]
    @instance_name sysname,
    @start_time datetime = NULL,
    @end_time datetime = NULL,
    @time_window_size smallint = NULL,
    @time_interval_min smallint = NULL
AS
BEGIN
    SET NOCOUNT ON;

    -- Convert snapshot_time (datetimeoffset) to a UTC datetime
    IF (@end_time IS NULL)
        SET @end_time = CONVERT (datetime, SWITCHOFFSET (CAST ((SELECT MAX(snapshot_time) FROM core.snapshots) AS datetimeoffset(7)), '+00:00'));

    IF (@start_time IS NULL)
    BEGIN
        -- If time_window_size and time_interval_min are set use them
        -- to determine the start time
        -- Otherwise use the earliest available snapshot_time
        IF @time_window_size IS NOT NULL AND @time_interval_min IS NOT NULL
        BEGIN
            SET @start_time = DATEADD(minute, @time_window_size * @time_interval_min * -1.0, @end_time);
        END
        ELSE
        BEGIN
            -- Convert min snapshot_time (datetimeoffset) to a UTC datetime
            SET @start_time = CONVERT (datetime, SWITCHOFFSET (CAST ((SELECT MIN(snapshot_time) FROM core.snapshots) AS datetimeoffset(7)), '+00:00'));
        END
    END

    DECLARE @end_snapshot_time_id int;
    SELECT @end_snapshot_time_id = MAX(snapshot_time_id) FROM core.snapshots WHERE snapshot_time <= @end_time;

    DECLARE @start_snapshot_time_id int;
    SELECT @start_snapshot_time_id = MIN(snapshot_time_id) FROM core.snapshots WHERE snapshot_time >= @start_time;


    SELECT 
        SUBSTRING(pc.path, CHARINDEX(N'\', pc.path, 2)+1, LEN(pc.path) - CHARINDEX(N'\', pc.path, 2)) as series,
        s.snapshot_time_id,
        CONVERT (datetime, SWITCHOFFSET (CAST (s.snapshot_time AS datetimeoffset(7)), '+00:00')) AS snapshot_time, 
        CONVERT (datetime, SWITCHOFFSET (CAST (pc.collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        pc.formatted_value
    FROM snapshots.performance_counters pc
    JOIN core.snapshots s ON (s.snapshot_id = pc.snapshot_id)
    WHERE s.instance_name = @instance_name
        AND s.snapshot_time_id BETWEEN @start_snapshot_time_id AND @end_snapshot_time_id
        AND (pc.performance_object_name LIKE '%SQL%:General Statistics' OR pc.performance_object_name LIKE '%SQL%:SQL Statistics')
        AND pc.performance_counter_name IN ('Logins/sec', 'Logouts/sec', 'Batch Requests/sec', 'Transactions', 
            'User Connections', 'SQL Compilations/sec', 'SQL Re-Compilations/sec')
    ORDER BY 
        pc.collection_time, series
    -- These trace flags are necessary for a good plan, due to the join on ascending PK w/range filter
    OPTION (QUERYTRACEON 2389, QUERYTRACEON 2390)
END;
GO

GRANT EXECUTE ON [snapshots].[rpt_server_activity]                      TO [mdw_reader]
GO

--
-- snapshots.rpt_wait_stats_one_snapshot_one_category
--  Returns details of wait stats for one snapshot and one wait category
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @snapshot_time_id - time window identifier (snapshot ID from core.snapshots) 
--    @category_name - Name of wait category to filter on
--
IF (NOT OBJECT_ID(N'snapshots.rpt_wait_stats_one_snapshot_one_category', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[rpt_wait_stats_one_snapshot_one_category] ...'
    DROP PROCEDURE [snapshots].[rpt_wait_stats_one_snapshot_one_category]
END
GO 

PRINT 'Creating procedure [snapshots].[rpt_wait_stats_one_snapshot_one_category] ...'
GO
CREATE PROCEDURE [snapshots].[rpt_wait_stats_one_snapshot_one_category]
    @instance_name sysname,
    @snapshot_time_id int,
    @category_name nvarchar(20)
AS
BEGIN
    SET NOCOUNT ON;
    
    -- Use CTE to select first all raw data that we neeed
    -- For each snapshot append a [rank] column, which later will be used to do the self-join
    WITH wait_times AS 
    (
    -- First part gets resource wait times for each wait_type,
    -- calculated as wait_time_ms - signal_wait_time_ms 
    SELECT 
        DENSE_RANK() OVER (ORDER BY ws1.collection_time) AS [rank],
        s.snapshot_time_id,
        ws1.collection_time, 
        ct.category_name,
        ev.wait_type,
        ws1.waiting_tasks_count,
        ws1.wait_time_ms - ws1.signal_wait_time_ms as resource_wait_time_ms
    FROM core.snapshots s
    JOIN snapshots.os_wait_stats ws1 on (ws1.snapshot_id = s.snapshot_id)
    JOIN core.wait_types ev on (ev.wait_type = ws1.wait_type)
    JOIN core.wait_categories ct on (ct.category_id = ev.category_id)
    WHERE ct.category_name = @category_name
      AND s.instance_name = @instance_name
      AND (s.snapshot_time_id = @snapshot_time_id OR s.snapshot_time_id = @snapshot_time_id - 1)
    )
    -- Do a self-join to calculate deltas between snapshots
    SELECT 
        CONVERT (datetime, SWITCHOFFSET (CAST (t1.collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        t1.wait_type,
        (t1.waiting_tasks_count - t2.waiting_tasks_count) AS waiting_tasks_count_delta,
        (t1.resource_wait_time_ms - ISNULL(t2.resource_wait_time_ms,0))/CAST(DATEDIFF(second, t2.collection_time, t1.collection_time) AS decimal) AS resource_wait_time_delta
    FROM wait_times t1
    LEFT OUTER JOIN wait_times t2 on (t2.rank = t1.rank-1 and t2.wait_type = t1.wait_type)
    WHERE t1.snapshot_time_id = @snapshot_time_id
    ORDER BY collection_time, wait_type
END;
GO
GRANT EXECUTE ON [snapshots].[rpt_wait_stats_one_snapshot_one_category]  TO [mdw_reader]
GO


/**********************************************************************/
/* Used by CTP6 and CTP6 Refresh/RC0 (but not RTM):                   */
/**********************************************************************/
--
-- snapshots.rpt_sql_process_memory
--  Returns details of the SQL process' memory usage
-- Parameters: 
--    @ServerName - SQL Server instance name
--    @EndTime - End of the user-selected time window (UTC)
--    @WindowSize - Number of minutes in the time window 
--
IF (NOT OBJECT_ID(N'snapshots.rpt_sql_process_memory', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [snapshots].[rpt_sql_process_memory] ...'
    DROP PROCEDURE [snapshots].[rpt_sql_process_memory]
END
GO 

PRINT 'Creating procedure [snapshots].[rpt_sql_process_memory] ...'
GO
CREATE PROCEDURE [snapshots].[rpt_sql_process_memory]
    @ServerName sysname,
    @EndTime datetime = NULL,
    @WindowSize int
AS
BEGIN
    SET NOCOUNT ON;
    
    -- Divide our time window up into 40 evenly-sized time intervals, and find the last collection_time within each of these intervals
    CREATE TABLE #intervals (
        interval_time_id        int, 
        interval_start_time     datetimeoffset(7),
        interval_end_time       datetimeoffset(7),
        interval_id             int, 
        first_collection_time   datetimeoffset(7), 
        last_collection_time    datetimeoffset(7), 
        first_snapshot_id       int,
        last_snapshot_id        int, 
        source_id               int, 
        snapshot_id             int, 
        collection_time         datetimeoffset(7), 
        collection_time_id      int
    )
    -- GUID 49268954-... is the Server Activity CS
    INSERT INTO #intervals
    EXEC [snapshots].[rpt_interval_collection_times] 
        @ServerName, @EndTime, @WindowSize, 'snapshots.os_process_memory', '49268954-4FD4-4EB6-AA04-CD59D9BB5714', 40, 0

    SELECT 
        CONVERT (datetime, SWITCHOFFSET (CAST (collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        CASE 
            WHEN series = 'virtual_address_space_reserved_kb' THEN 'Virtual Memory Reserved'
            WHEN series = 'virtual_address_space_committed_kb' THEN 'Virtual Memory Committed'
            WHEN series = 'physical_memory_in_use_kb' THEN 'Physical Memory In Use'
            WHEN series = 'process_physical_memory_low' THEN 'Physical Memory Low'
            WHEN series = 'process_virtual_memory_low' THEN 'Virtual Memory Low'
            ELSE series
        END AS series,
        [value] / 1024 AS [value] -- convert KB to MB
    FROM 
    (
        SELECT 
            pm.collection_time,
            pm.virtual_address_space_reserved_kb,
            pm.virtual_address_space_committed_kb,
            pm.physical_memory_in_use_kb,
            CONVERT (bigint, pm.process_physical_memory_low) AS process_physical_memory_low,
            CONVERT (bigint, pm.process_virtual_memory_low) AS process_virtual_memory_low
        FROM [snapshots].[os_process_memory] AS pm
        INNER JOIN #intervals AS i ON (i.last_snapshot_id = pm.snapshot_id AND i.last_collection_time = pm.collection_time)
    ) AS pvt
    UNPIVOT
    (
        [value] for [series] in 
            (virtual_address_space_reserved_kb, virtual_address_space_committed_kb, 
            physical_memory_in_use_kb)
    ) AS unpvt
END
GO
GRANT EXECUTE ON [snapshots].[rpt_sql_process_memory]                   TO [mdw_reader]
GO



/**********************************************************************/
/* END OF LEGACY OBJECTS                                              */
/**********************************************************************/



/**********************************************************************/
/* OPERATOR CHECK                                                     */
/**********************************************************************/

-- This trigger needs to be defined after we have created all our tables,
-- otherwise it would interfere with them
IF EXISTS (SELECT object_id FROM sys.triggers WHERE name = N'add_operator_check' AND type = N'TR')
BEGIN
    PRINT 'Dropping database trigger [add_operator_check] ...'
    DROP TRIGGER [add_operator_check] ON DATABASE
END
GO 

PRINT 'Creating database trigger [add_operator_check] ...'
GO
CREATE TRIGGER [add_operator_check]
ON DATABASE
WITH EXECUTE AS 'mdw_check_operator_admin'
FOR CREATE_TABLE 
AS 
BEGIN
    DECLARE @schema_name sysname;
    DECLARE @table_name sysname;

    -- Set options required by the rest of the code in this SP.
    SET ANSI_NULLS ON
    SET ANSI_PADDING ON
    SET ANSI_WARNINGS ON
    SET ARITHABORT ON
    SET CONCAT_NULL_YIELDS_NULL ON
    SET NUMERIC_ROUNDABORT OFF
    SET QUOTED_IDENTIFIER ON 


    SELECT @schema_name = EVENTDATA().value('(/EVENT_INSTANCE/SchemaName)[1]', 'sysname')
	IF (@schema_name = N'custom_snapshots')
	BEGIN
		SELECT @table_name = EVENTDATA().value('(/EVENT_INSTANCE/ObjectName)[1]', 'sysname')

		-- Dynamically add a constraint on the newly created table
		-- Table must have the snapshot_id column
		DECLARE @check_name sysname;
		SELECT @check_name = N'CHK_check_operator_' + CONVERT(nvarchar(36), NEWID());
		DECLARE @sql nvarchar(2000);
		SELECT @sql = N'ALTER TABLE ' + QUOTENAME(@schema_name) + N'.' + QUOTENAME(@table_name) +
					  N' ADD CONSTRAINT ' + QUOTENAME(@check_name) + ' CHECK (core.fn_check_operator(snapshot_id) = 1);';
		EXEC(@sql);
	    
		-- Dynamically revoke the CONTROL right on the table for mdw_writer
		-- That way mdw_writer creates the table but cannot remove it or alter it
		SELECT @sql = N'DENY ALTER ON ' + QUOTENAME(@schema_name) + N'.' + QUOTENAME(@table_name) +
					  N'TO [mdw_writer]';
		EXEC(@sql);
    END
END;
GO



IF EXISTS (SELECT object_id FROM sys.triggers WHERE name = N'deny_drop_table' AND type = N'TR')
BEGIN
    PRINT 'Dropping database trigger [deny_drop_table] ...'
    DROP TRIGGER [deny_drop_table] ON DATABASE
END
GO 

PRINT 'Creating database trigger [deny_drop_table] ...'
GO
CREATE TRIGGER [deny_drop_table]
ON DATABASE
FOR DROP_TABLE 
AS 
BEGIN
    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'mdw_admin'), 0) = 1) AND NOT (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1))
    BEGIN
        RAISERROR(14677, 16, -1, 'mdw_admin');
    END;
END;
GO

CHECKPOINT
GO

/**********************************************************************/
/* install system collector types                                     */
/**********************************************************************/
PRINT ''
PRINT 'Adding collector types...'
GO

-- Install TSQL Query collector type
EXEC [core].[sp_add_collector_type] @collector_type_uid = '302e93d1-3424-4be7-aa8e-84813ecf2419'
GO

-- Install SqlTrace collector type
EXEC [core].[sp_add_collector_type] @collector_type_uid = '0E218CF8-ECB5-417B-B533-D851C0251271'
GO

-- Install Performance Counters collector type
EXEC [core].[sp_add_collector_type] @collector_type_uid = '294605dd-21de-40b2-b20f-f3e170ea1ec3'

-- Install Query Activity collectory type
EXEC [core].[sp_add_collector_type] @collector_type_uid = '14AF3C12-38E6-4155-BD29-F33E7966BA23'
GO



/**********************************************************************/
/* Data Purge job                                            */
/**********************************************************************/
PRINT ''
PRINT 'Creating purge data job...'
GO

BEGIN TRY
    BEGIN TRANSACTION;

    DECLARE @job_owner NVARCHAR(256);
    SELECT @job_owner = SUSER_SNAME();

    DECLARE @db_name sysname
    SELECT @db_name = DB_NAME();

	DECLARE @job_name_for_purge sysname
	SELECT @job_name_for_purge = N'mdw_purge_data_' + QUOTENAME(@db_name)

    DECLARE @job_id UNIQUEIDENTIFIER; 
    SELECT @job_id = job_id FROM [msdb].[dbo].[sysjobs_view] WHERE [name] = @job_name_for_purge
    IF @job_id IS NULL
    BEGIN 
        -- Add job category if it does not exist. On Katmai server this will be already created.
        IF NOT EXISTS (SELECT name FROM msdb.dbo.syscategories WHERE name=N'Data Collector' AND category_class=1)
        BEGIN
            EXEC msdb.dbo.sp_add_category @class=N'JOB', @type=N'LOCAL', @name=N'Data Collector'
        END

        -- Add job
        EXEC msdb.dbo.sp_add_job 
                @job_name=@job_name_for_purge, 
                @enabled=1, 
                @notify_level_eventlog=0, 
                @notify_level_email=0, 
                @notify_level_netsend=0, 
                @notify_level_page=0, 
                @delete_level=0, 
                @description=N'Runs every day and removes data from Management Data Warehouse database that reached its expiration date.', 
                @category_name=N'Data Collector', 
                @owner_login_name=@job_owner,
                @job_id = @job_id OUTPUT;

        -- Add job step
        EXEC msdb.dbo.sp_add_jobstep 
                @job_id=@job_id, 
                @step_name=N'Step 1', 
                @step_id=1, 
                @cmdexec_success_code=0, 
                @on_success_action=1, 
                @on_success_step_id=0, 
                @on_fail_action=2, 
                @on_fail_step_id=0, 
                @retry_attempts=0, 
                @retry_interval=0, 
                @os_run_priority=0, 
                @subsystem=N'TSQL', 
                @command=N'exec core.sp_purge_data', 
                @database_name=@db_name, 
                @flags=4;

        -- Set the job step to start
        EXEC msdb.dbo.sp_update_job @job_id = @job_id, @start_step_id = 1;

        -- Add schedule
        EXEC msdb.dbo.sp_add_jobschedule 
                @job_id=@job_id, 
                @name=N'mdw_purge_data_schedule', 
                @enabled=1, 
                @freq_type=4, 
                @freq_interval=1, 
                @freq_subday_type=1, 
                @freq_subday_interval=0, 
                @freq_relative_interval=0, 
                @freq_recurrence_factor=0, 
                @active_start_time=20000;

        -- Set the job server
        EXEC msdb.dbo.sp_add_jobserver @job_id = @job_id, @server_name = N'(local)';
    END
    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    IF (@@TRANCOUNT > 0) 
        ROLLBACK TRANSACTION

    -- Rethrow the error
    DECLARE @ErrorMessage   NVARCHAR(4000);
    DECLARE @ErrorSeverity  INT;
    DECLARE @ErrorState     INT;
    DECLARE @ErrorNumber    INT;
    DECLARE @ErrorLine      INT;
    DECLARE @ErrorProcedure NVARCHAR(200);
    SELECT @ErrorLine = ERROR_LINE(),
           @ErrorSeverity = ERROR_SEVERITY(),
           @ErrorState = ERROR_STATE(),
           @ErrorNumber = ERROR_NUMBER(),
           @ErrorMessage = ERROR_MESSAGE(),
           @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');

    RAISERROR (14684, -1, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);

END CATCH
GO

-- Commit our transaction
COMMIT TRANSACTION
GO

-- Put the database back into multi user mode
PRINT ''
PRINT 'Restoring database to multi user mode'
DECLARE @dbname sysname
SET @dbname = QUOTENAME(DB_NAME())

DECLARE @sql_db_multi_mode nvarchar(256)
SET @sql_db_multi_mode = 'ALTER DATABASE ' + @dbname +
                                    ' SET MULTI_USER WITH ROLLBACK IMMEDIATE'
EXEC sp_executesql @sql_db_multi_mode

-- check if sync auto stats was on
IF (EXISTS (SELECT * FROM #tmp_auto_mode))  -- if yes, turn it back on
BEGIN
    PRINT ''
    PRINT 'Re-enabling asynchronous auto statistics ...'
    DECLARE @sql_async_autostat_on nvarchar(256)
    SET @sql_async_autostat_on = 'ALTER DATABASE ' + @dbname +
                                        ' SET AUTO_UPDATE_STATISTICS_ASYNC ON'
    EXEC sp_executesql @sql_async_autostat_on
END
DROP TABLE #tmp_auto_mode

PRINT ''
PRINT '----------------------------------'
PRINT 'Execution of INSTMDW.SQL complete'
PRINT '----------------------------------'
GO