/**********************************************************************/ /* 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 (c) Microsoft Corporation */ /* All Rights Reserved. */ /* */ /**********************************************************************/ RAISERROR('', 0, 1) WITH NOWAIT; RAISERROR('----------------------------------', 0, 1) WITH NOWAIT; RAISERROR('Starting execution of INSTMDW.SQL ', 0, 1) WITH NOWAIT; RAISERROR('----------------------------------', 0, 1) WITH NOWAIT; GO SET ANSI_NULLS ON SET ANSI_NULL_DFLT_ON 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 -- 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 RAISERROR('', 0, 1) WITH NOWAIT; RAISERROR('Taking database to single user mode', 0, 1) WITH NOWAIT; 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 RAISERROR('Disabling asynchronous auto statistics while database in single user mode ...', 0, 1) WITH NOWAIT; 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_query nvarchar(256) SET @sql_query = 'ALTER DATABASE ' + @dbname + ' SET SINGLE_USER WITH ROLLBACK IMMEDIATE' EXEC sp_executesql @sql_query; -- Allow the use of snapshot transaction isolation level SET @sql_query = 'ALTER DATABASE ' + @dbname + ' SET ALLOW_SNAPSHOT_ISOLATION ON'; EXEC sp_executesql @sql_query; -- Turn on the read_committed_snapshot database option SET @sql_query = 'ALTER DATABASE ' + @dbname + ' SET READ_COMMITTED_SNAPSHOT ON'; EXEC sp_executesql @sql_query; 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 = '11.0.2100.60' -- 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 <> '11.0.2100.60') -- 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 RAISERROR('', 0, 1) WITH NOWAIT; RAISERROR('Restoring database to multi user mode before aborting the script', 0, 1) WITH NOWAIT; 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 COLLATE CATALOG_DEFAULT, column_name sysname COLLATE CATALOG_DEFAULT, 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 COLLATE CATALOG_DEFAULT, column_name sysname COLLATE CATALOG_DEFAULT, 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; -- Alter column length for [command] as 32; column : [command] sys.dm_exec_requests in SQL 11 is of type nvarchar(32) IF EXISTS (SELECT c.system_type_id FROM sys.columns c WHERE c.object_id = OBJECT_ID('snapshots.active_sessions_and_requests', 'U') AND c.name = N'command') BEGIN RAISERROR ('Updating [command] column length to 32...', 0, 1) WITH NOWAIT; ALTER TABLE [snapshots].[active_sessions_and_requests] ALTER COLUMN [command] nvarchar(32) 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 */ /**********************************************************************/ RAISERROR('', 0, 1) WITH NOWAIT; RAISERROR('Create schema core...', 0, 1) WITH NOWAIT; 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 -- RAISERROR('', 0, 1) WITH NOWAIT; GO IF (OBJECT_ID(N'[core].[supported_collector_types_internal]', 'U') IS NULL) BEGIN RAISERROR('Creating table [core].[supported_collector_types_internal]...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping view [core].[supported_collector_types]...', 0, 1) WITH NOWAIT; DROP VIEW core.supported_collector_types END GO RAISERROR('Creating view [core].[supported_collector_types]...', 0, 1) WITH NOWAIT; 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 RAISERROR('Creating table [core].[source_info_internal]...', 0, 1) WITH NOWAIT; 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 RAISERROR('Creating table [core].[snapshot_timetable_internal]...', 0, 1) WITH NOWAIT; 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 RAISERROR('Creating table [core].[snapshots_internal]...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping view [core].[snapshots]...', 0, 1) WITH NOWAIT; DROP VIEW core.snapshots END GO RAISERROR('Creating view [core].[snapshots]...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [core].[sp_create_snapshot] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [core].[sp_create_snapshot] END GO RAISERROR('Creating procedure [core].[sp_create_snapshot] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [core].[sp_update_data_source] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [core].[sp_update_data_source] END GO RAISERROR('Creating procedure [core].[sp_update_data_source] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [core].[sp_add_collector_type] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [core].[sp_add_collector_type] END GO RAISERROR('Creating procedure [core].[sp_add_collector_type] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [core].[sp_remove_collector_type] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [core].[sp_remove_collector_type] END GO RAISERROR('Creating procedure [core].[sp_remove_collector_type] ...', 0, 1) WITH NOWAIT; 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. -- RAISERROR('', 0, 1) WITH NOWAIT; GO IF (OBJECT_ID(N'[core].[purge_info_internal]', 'U') IS NULL) BEGIN RAISERROR('Creating table [core].[purge_info_internal]...', 0, 1) WITH NOWAIT; CREATE TABLE [core].[purge_info_internal] ( stop_purge bit NOT NULL, ) ON [PRIMARY] END GO -- -- This stored proc removes orphaned notable_query_plan data from the warehouse -- IF (NOT OBJECT_ID(N'core.sp_purge_orphaned_notable_query_plan', 'P') IS NULL) BEGIN RAISERROR('Dropping procedure [core].[sp_purge_orphaned_notable_query_plan] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [core].[sp_purge_orphaned_notable_query_plan] END GO RAISERROR('Creating procedure [core].[sp_purge_orphaned_notable_query_plan] ...', 0, 1) WITH NOWAIT; GO CREATE PROCEDURE [core].[sp_purge_orphaned_notable_query_plan] @duration smallint = NULL, @end_time datetime = NULL, @delete_batch_size int = 500 AS BEGIN PRINT 'Begin purging orphaned records in snapshots.notable_query_plan Current UTC Time:' + CONVERT(VARCHAR, GETUTCDATE()) DECLARE @stop_purge int -- Delete orphaned rows from snapshots.notable_query_plan. Query plans are not deleted by the generic purge -- process that deletes other data (above) because query plan rows are not tied to a particular snapshot ID. -- Purging query plans table as a special case, by looking for plans that -- are no longer referenced by any of the rows in the snapshots.query_stats table. We need to delete these -- rows in small chunks, since deleting many GB in a single delete statement would cause lock escalation and -- an explosion in the size of the transaction log (individual query plans can be 10-50MB). DECLARE @rows_affected int; -- set expected rows affected as delete batch size SET @rows_affected = @delete_batch_size; -- select set of orphaned query plans to be deleted into a temp table SELECT qp.[sql_handle], qp.plan_handle, qp.plan_generation_num, qp.statement_start_offset, qp.statement_end_offset, qp.creation_time INTO #tmp_notable_query_plan FROM snapshots.notable_query_plan AS qp WHERE NOT EXISTS ( SELECT snapshot_id FROM snapshots.query_stats AS qs WHERE qs.[sql_handle] = qp.[sql_handle] AND qs.plan_handle = qp.plan_handle AND qs.plan_generation_num = qp.plan_generation_num AND qs.statement_start_offset = qp.statement_start_offset AND qs.statement_end_offset = qp.statement_end_offset AND qs.creation_time = qp.creation_time) WHILE (@rows_affected = @delete_batch_size) BEGIN -- Deleting TOP N orphaned rows in query plan table by joining info from temp table variable -- This is done to speed up delete query. DELETE TOP (@delete_batch_size) snapshots.notable_query_plan FROM snapshots.notable_query_plan AS qp , #tmp_notable_query_plan AS tmp WHERE tmp.[sql_handle] = qp.[sql_handle] AND tmp.plan_handle = qp.plan_handle AND tmp.plan_generation_num = qp.plan_generation_num AND tmp.statement_start_offset = qp.statement_start_offset AND tmp.statement_end_offset = qp.statement_end_offset AND tmp.creation_time = qp.creation_time SET @rows_affected = @@ROWCOUNT; IF(@rows_affected > 0) BEGIN RAISERROR ('Deleted %d orphaned rows from snapshots.notable_query_plan', 0, 1, @rows_affected) WITH NOWAIT; 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(VARCHAR, @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 END; PRINT 'End purging orphaned records in snapshots.notable_query_plan Current UTC Time:' + CONVERT(VARCHAR, GETUTCDATE()) END GO -- -- This stored proc removes orphaned notable_query_text data from the warehouse -- IF (NOT OBJECT_ID(N'core.sp_purge_orphaned_notable_query_text', 'P') IS NULL) BEGIN RAISERROR('Dropping procedure [core].[sp_purge_orphaned_notable_query_text] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [core].[sp_purge_orphaned_notable_query_text] END GO RAISERROR('Creating procedure [core].[sp_purge_orphaned_notable_query_text] ...', 0, 1) WITH NOWAIT; GO CREATE PROCEDURE [core].[sp_purge_orphaned_notable_query_text] @duration smallint = NULL, @end_time datetime = NULL, @delete_batch_size int = 500 AS BEGIN PRINT 'Begin purging orphaned records in snapshots.notable_query_text Current UTC Time:' + CONVERT(VARCHAR, GETUTCDATE()) DECLARE @stop_purge int -- Delete orphaned rows from snapshots.notable_query_text. Query texts are not deleted by the generic purge -- process that deletes other data (above) because query text rows are not tied to a particular snapshot ID. -- Purging query text table as a special case, by looking for plans that -- are no longer referenced by any of the rows in the snapshots.query_stats table. We need to delete these -- rows in small chunks, since deleting many GB in a single delete statement would cause lock escalation and -- an explosion in the size of the transaction log (individual query plans can be 10-50MB). DECLARE @rows_affected int; -- set expected rows affected as delete batch size SET @rows_affected = @delete_batch_size; SELECT qt.[sql_handle] INTO #tmp_notable_query_text FROM snapshots.notable_query_text AS qt WHERE NOT EXISTS ( SELECT snapshot_id FROM snapshots.query_stats AS qs WHERE qs.[sql_handle] = qt.[sql_handle]) WHILE (@rows_affected = @delete_batch_size) BEGIN -- Deleting TOP N orphaned rows in query text table by joining info from temp table -- This is done to speed up delete query. DELETE TOP (@delete_batch_size) snapshots.notable_query_text FROM snapshots.notable_query_text AS qt, #tmp_notable_query_text AS tmp WHERE tmp.[sql_handle] = qt.[sql_handle] SET @rows_affected = @@ROWCOUNT; IF(@rows_affected > 0) BEGIN RAISERROR ('Deleted %d orphaned rows from snapshots.notable_query_text', 0, 1, @rows_affected) WITH NOWAIT; 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(VARCHAR, @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 END; PRINT 'End purging orphaned records in snapshots.notable_query_text Current UTC Time:' + CONVERT(VARCHAR, GETUTCDATE()) 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 RAISERROR('Dropping procedure [core].[sp_purge_data] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [core].[sp_purge_data] END GO RAISERROR('Creating procedure [core].[sp_purge_data] ...', 0, 1) WITH NOWAIT; GO CREATE PROCEDURE [core].[sp_purge_data] @retention_days smallint = NULL, @instance_name sysname = NULL, @collection_set_uid uniqueidentifier = NULL, @duration smallint = NULL, @delete_batch_size int = 500 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(VARCHAR, @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(VARCHAR, @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 -- delete orphaned query plans EXEC [core].[sp_purge_orphaned_notable_query_plan] @duration = @duration, @end_time = @end_time, @delete_batch_size = @delete_batch_size -- delete orphaned query text EXEC [core].[sp_purge_orphaned_notable_query_text] @duration = @duration, @end_time = @end_time, @delete_batch_size = @delete_batch_size 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 RAISERROR('Dropping procedure [core].[sp_stop_purge] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [core].[sp_stop_purge] END GO RAISERROR('Creating procedure [core].[sp_stop_purge] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Creating function [core].[fn_check_operator] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping table [core].[wait_types] ...', 0, 1) WITH NOWAIT; DROP TABLE [core].[wait_types] END IF (NOT OBJECT_ID(N'core.wait_categories', 'U') IS NULL) BEGIN RAISERROR('Dropping table [core].[wait_categories] ...', 0, 1) WITH NOWAIT; DROP TABLE [core].[wait_categories] END RAISERROR('Creating table [core].[wait_categories] ...', 0, 1) WITH NOWAIT; 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. -- -- RAISERROR('Creating table [core].[wait_types] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping view [core].[wait_types_categorized] ...', 0, 1) WITH NOWAIT; DROP VIEW [core].[wait_types_categorized]; END; GO RAISERROR('Creating view [core].[wait_types_categorized] ...', 0, 1) WITH NOWAIT; 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'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 (13, N'FSAGENT'); 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 RAISERROR('Dropping function [core].[fn_query_text_from_handle] ...', 0, 1) WITH NOWAIT; DROP FUNCTION [core].[fn_query_text_from_handle] END GO RAISERROR('Creating function [core].[fn_query_text_from_handle] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping table [core].[performance_counter_report_group_items] ...', 0, 1) WITH NOWAIT; DROP TABLE [core].[performance_counter_report_group_items] END GO RAISERROR('Creating table [core].[performance_counter_report_group_items] ...', 0, 1) WITH NOWAIT; 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', 'System', 'Process', 0, 'IO Write Bytes/sec', '_Total', NULL, @CONVERT_BYTES_TO_MB, 0 ), -- changed to fix Bug # 234905 ('ServerActivity', 'IOUsage', 'SQL Server', 'Process', 0, 'IO Read Bytes/sec', '$(TARGETPROCESS)', NULL, @CONVERT_BYTES_TO_MB, 0 ), ('ServerActivity', 'IOUsage', 'SQL Server', 'Process', 0, 'IO Write Bytes/sec', '$(TARGETPROCESS)', NULL, @CONVERT_BYTES_TO_MB, 0 ), -- changed to fix Bug # 234905 ('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 */ /**********************************************************************/ RAISERROR('', 0, 1) WITH NOWAIT; RAISERROR('Create schema snapshots...', 0, 1) WITH NOWAIT; RAISERROR('', 0, 1) WITH NOWAIT; 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 RAISERROR('Creating table [snapshots].[performance_counter_instances]...', 0, 1) WITH NOWAIT; 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 RAISERROR('Creating table [snapshots].[performance_counter_values]...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping view [snapshots].[performance_counters]...', 0, 1) WITH NOWAIT; DROP VIEW [snapshots].[performance_counters] END RAISERROR('Creating view [snapshots].[performance_counters]...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping function [snapshots].[fn_get_performance_counters] ...', 0, 1) WITH NOWAIT; DROP FUNCTION [snapshots].[fn_get_performance_counters] END GO RAISERROR('Creating function [snapshots].[fn_get_performance_counters] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping function [snapshots].[fn_get_performance_counter_statistics] ...', 0, 1) WITH NOWAIT; DROP FUNCTION [snapshots].[fn_get_performance_counter_statistics] END GO RAISERROR('Creating function [snapshots].[fn_get_performance_counter_statistics] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Creating table [snapshots].[trace_info]...', 0, 1) WITH NOWAIT; 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 RAISERROR('Creating table [snapshots].[trace_data]...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [snapshots].[sp_trace_get_info] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[sp_trace_get_info] END GO RAISERROR('Creating procedure [snapshots].[sp_trace_get_info] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [snapshots].[sp_trace_update_info] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[sp_trace_update_info] END GO RAISERROR('Creating procedure [snapshots].[sp_trace_update_info] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping function [snapshots].[fn_trace_gettable] ...', 0, 1) WITH NOWAIT; DROP FUNCTION [snapshots].[fn_trace_gettable] END GO RAISERROR('Creating function [snapshots].[fn_trace_gettable] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Creating table [snapshots].[notable_query_text]...', 0, 1) WITH NOWAIT; 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 RAISERROR('Creating table [snapshots].[notable_query_plan]...', 0, 1) WITH NOWAIT; 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 RAISERROR('Creating table [snapshots].[distinct_queries]...', 0, 1) WITH NOWAIT; 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 RAISERROR('Creating table [snapshots].[distinct_query_to_handle]...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping function [snapshots].[fn_get_query_text] ...', 0, 1) WITH NOWAIT; DROP FUNCTION [snapshots].[fn_get_query_text] END GO RAISERROR('Creating function [snapshots].[fn_get_query_text] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [snapshots].[sp_get_unknown_query_text] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[sp_get_unknown_query_text] END GO RAISERROR('Creating procedure [snapshots].[sp_get_unknown_query_text] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [snapshots].[sp_get_unknown_query_plan] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[sp_get_unknown_query_plan] END GO RAISERROR('Creating procedure [snapshots].[sp_get_unknown_query_plan] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [snapshots].[sp_update_query_text] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[sp_update_query_text] END GO RAISERROR('Creating procedure [snapshots].[sp_update_query_text] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [snapshots].[sp_update_query_plan] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[sp_update_query_plan] END GO RAISERROR('Creating procedure [snapshots].[sp_update_query_plan] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Creating table [snapshots].[os_wait_stats]...', 0, 1) WITH NOWAIT; 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 RAISERROR('Creating table [snapshots].[os_latch_stats]...', 0, 1) WITH NOWAIT; 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 RAISERROR('Creating table [snapshots].[active_sessions_and_requests]...', 0, 1) WITH NOWAIT; 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(32) 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 RAISERROR('Creating table [snapshots].[os_schedulers]...', 0, 1) WITH NOWAIT; 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 RAISERROR('Creating table [snapshots].[os_memory_nodes]...', 0, 1) WITH NOWAIT; 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 RAISERROR('Creating table [snapshots].[os_process_memory]...', 0, 1) WITH NOWAIT; 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 RAISERROR('Creating table [snapshots].[sql_process_and_system_memory]...', 0, 1) WITH NOWAIT; 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 RAISERROR('Creating table [snapshots].[os_memory_clerks]...', 0, 1) WITH NOWAIT; 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 RAISERROR('Creating table [snapshots].[query_stats]...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping view [snapshots].[distinct_query_stats]...', 0, 1) WITH NOWAIT; DROP VIEW [snapshots].[distinct_query_stats] END GO RAISERROR('Creating view [snapshots].[distinct_query_stats]...', 0, 1) WITH NOWAIT; 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 RAISERROR('Creating table [snapshots].[disk_usage]...', 0, 1) WITH NOWAIT; 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 RAISERROR('Creating table [snapshots].[log_usage]...', 0, 1) WITH NOWAIT; 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 RAISERROR('Creating table [snapshots].[io_virtual_file_stats]...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [snapshots].[rpt_snapshot_times] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[rpt_snapshot_times] END GO RAISERROR('Creating procedure [snapshots].[rpt_snapshot_times] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [snapshots].[rpt_interval_collection_times] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[rpt_interval_collection_times] END GO RAISERROR('Creating procedure [snapshots].[rpt_interval_collection_times] ...', 0, 1) WITH NOWAIT; 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, (<> * @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 RAISERROR('Dropping procedure [snapshots].[rpt_next_and_previous_collection_times] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[rpt_next_and_previous_collection_times] END GO RAISERROR('Creating procedure [snapshots].[rpt_next_and_previous_collection_times] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [snapshots].[rpt_generic_perfmon] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[rpt_generic_perfmon] END GO RAISERROR('Creating procedure [snapshots].[rpt_generic_perfmon] ...', 0, 1) WITH NOWAIT; 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() * 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 RAISERROR('Dropping procedure [snapshots].[rpt_generic_perfmon_pivot] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[rpt_generic_perfmon_pivot] END GO RAISERROR('Creating procedure [snapshots].[rpt_generic_perfmon_pivot] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [snapshots].[rpt_wait_stats] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[rpt_wait_stats] END GO RAISERROR('Creating procedure [snapshots].[rpt_wait_stats] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping function [snapshots].[fn_hexstrtovarbin] ...', 0, 1) WITH NOWAIT; DROP FUNCTION [snapshots].[fn_hexstrtovarbin] END GO RAISERROR('Creating function [snapshots].[fn_hexstrtovarbin] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [snapshots].[rpt_top_query_stats] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[rpt_top_query_stats] END GO RAISERROR('Creating procedure [snapshots].[rpt_top_query_stats] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [snapshots].[rpt_query_stats] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[rpt_query_stats] END GO RAISERROR('Creating procedure [snapshots].[rpt_query_stats] ...', 0, 1) WITH NOWAIT; 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 plan_number) AS plan_count FROM ( SELECT *, DENSE_RANK() OVER (ORDER BY plan_handle, creation_time) AS plan_number 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 RAISERROR('Dropping procedure [snapshots].[rpt_query_plan_stats] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[rpt_query_plan_stats] END GO RAISERROR('Creating procedure [snapshots].[rpt_query_plan_stats] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [snapshots].[rpt_query_plan_stats_timeline] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[rpt_query_plan_stats_timeline] END GO RAISERROR('Creating procedure [snapshots].[rpt_query_plan_stats_timeline] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [snapshots].[rpt_query_plan_missing_indexes] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[rpt_query_plan_missing_indexes] END GO RAISERROR('Creating procedure [snapshots].[rpt_query_plan_missing_indexes] ...', 0, 1) WITH NOWAIT; 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 fragment. Some query plans may be too complex -- to represent in the T-SQL xml data type, but the 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 = '' 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 fragment from an XML query plan: -- -- -- -- -- -- -- -- -- -- -- -- -- 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 RAISERROR('Dropping procedure [snapshots].[rpt_query_plan_parameters] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].rpt_query_plan_parameters END GO RAISERROR('Creating procedure [snapshots].[rpt_query_plan_parameters] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [snapshots].[rpt_query_plan_details] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].rpt_query_plan_details END GO RAISERROR('Creating procedure [snapshots].[rpt_query_plan_details] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [snapshots].[rpt_blocking_chains] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[rpt_blocking_chains] END GO RAISERROR('Creating procedure [snapshots].[rpt_blocking_chains] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [snapshots].[rpt_blocking_chain_detail] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[rpt_blocking_chain_detail] END GO RAISERROR('Creating procedure [snapshots].[rpt_blocking_chain_detail] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [snapshots].[rpt_active_sessions_and_requests] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[rpt_active_sessions_and_requests] END GO RAISERROR('Creating procedure [snapshots].[rpt_active_sessions_and_requests] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [snapshots].[rpt_sql_memory_clerks] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[rpt_sql_memory_clerks] END GO RAISERROR('Creating procedure [snapshots].[rpt_sql_memory_clerks] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [snapshots].[rpt_sql_process_and_system_memory] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[rpt_sql_process_and_system_memory] END GO RAISERROR('Creating procedure [snapshots].[rpt_sql_process_and_system_memory] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [snapshots].[rpt_sampled_waits] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[rpt_sampled_waits] END GO RAISERROR('Creating procedure [snapshots].[rpt_sampled_waits] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [snapshots].[rpt_sampled_waits_longest] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[rpt_sampled_waits_longest] END GO RAISERROR('Creating procedure [snapshots].[rpt_sampled_waits_longest] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [snapshots].[rpt_sampled_waits_hottest_resources] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[rpt_sampled_waits_hottest_resources] END GO RAISERROR('Creating procedure [snapshots].[rpt_sampled_waits_hottest_resources] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [snapshots].[rpt_io_virtual_file_stats] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[rpt_io_virtual_file_stats] END GO RAISERROR('Creating procedure [snapshots].[rpt_io_virtual_file_stats] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [snapshots].[rpt_list_all_servers] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[rpt_list_all_servers] END GO RAISERROR('Creating procedure [snapshots].[rpt_list_all_servers] ...', 0, 1) WITH NOWAIT; 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 */ /**********************************************************************/ RAISERROR('', 0, 1) WITH NOWAIT; RAISERROR('Create schema custom_snpshots...', 0, 1) WITH NOWAIT; 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 */ /**********************************************************************/ RAISERROR('', 0, 1) WITH NOWAIT; RAISERROR('Create mdw_admin role...', 0, 1) WITH NOWAIT; 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 RAISERROR('Create mdw_writer role...', 0, 1) WITH NOWAIT; 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 RAISERROR('Create mdw_reader role...', 0, 1) WITH NOWAIT; 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 RAISERROR('', 0, 1) WITH NOWAIT; RAISERROR('Create loginless user ...', 0, 1) WITH NOWAIT; 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 */ /**********************************************************************/ RAISERROR('', 0, 1) WITH NOWAIT; RAISERROR('Granting permissions to MDW security roles...', 0, 1) WITH NOWAIT; -- Database level permissions GRANT VIEW DEFINITION TO [mdw_writer] -- Core schema permissions -- custom_snaphots use the core.snapshots_internal.snapshot_id column as a FK, -- REFERENCES permission need to be granted to mdw_admin GRANT REFERENCES ON SCHEMA::[core] TO [mdw_admin] 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 RAISERROR('Dropping procedure [snapshots].[rpt_waiting_sessions] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[rpt_waiting_sessions] END GO RAISERROR('Creating procedure [snapshots].[rpt_waiting_sessions] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping function [snapshots].[fn_get_notable_query_plans] ...', 0, 1) WITH NOWAIT; DROP FUNCTION [snapshots].[fn_get_notable_query_plans] END RAISERROR('Creating function [snapshots].[fn_get_notable_query_plans]', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping function [snapshots].[fn_get_notable_query_text] ...', 0, 1) WITH NOWAIT; DROP FUNCTION [snapshots].[fn_get_notable_query_text] END RAISERROR('Creating function [snapshots].[fn_get_notable_query_text]', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [snapshots].[rpt_sql_memory_counters_one_snapshot] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[rpt_sql_memory_counters_one_snapshot] END GO RAISERROR('Creating procedure [snapshots].[rpt_sql_memory_counters_one_snapshot] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [snapshots].[rpt_sql_memory_clerks_one_snapshot] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[rpt_sql_memory_clerks_one_snapshot] END GO RAISERROR('Creating procedure [snapshots].[rpt_sql_memory_clerks_one_snapshot] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [snapshots].[rpt_sql_process_memory_one_snapshot] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[rpt_sql_process_memory_one_snapshot] END GO RAISERROR('Creating procedure [snapshots].[rpt_sql_process_memory_one_snapshot] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [snapshots].[rpt_memory_counters_one_snapshot] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[rpt_memory_counters_one_snapshot] END GO RAISERROR('Creating procedure [snapshots].[rpt_memory_counters_one_snapshot] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [snapshots].[rpt_memory_rates_one_snapshot] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[rpt_memory_rates_one_snapshot] END GO RAISERROR('Creating procedure [snapshots].[rpt_memory_rates_one_snapshot] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [snapshots].[rpt_cpu_counters_one_snapshot] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[rpt_cpu_counters_one_snapshot] END GO RAISERROR('Creating procedure [snapshots].[rpt_cpu_counters_one_snapshot] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [snapshots].[rpt_cpu_queues_one_snapshot] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[rpt_cpu_queues_one_snapshot] END GO RAISERROR('Creating procedure [snapshots].[rpt_cpu_queues_one_snapshot] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [snapshots].[rpt_cpu_usage_per_process] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[rpt_cpu_usage_per_process] END GO RAISERROR('Creating procedure [snapshots].[rpt_cpu_usage_per_process] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [snapshots].[rpt_sessions_and_connections] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[rpt_sessions_and_connections] END GO RAISERROR('Creating procedure [snapshots].[rpt_sessions_and_connections] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [snapshots].[rpt_requests_and_compilations] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[rpt_requests_and_compilations] END GO RAISERROR('Creating procedure [snapshots].[rpt_requests_and_compilations] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [snapshots].[rpt_plan_cache_hit_ratio] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[rpt_plan_cache_hit_ratio] END GO RAISERROR('Creating procedure [snapshots].[rpt_plan_cache_hit_ratio] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [snapshots].[rpt_tempdb] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[rpt_tempdb] END GO RAISERROR('Creating procedure [snapshots].[rpt_tempdb] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [snapshots].[rpt_disk_speed_one_snapshot] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[rpt_disk_speed_one_snapshot] END GO RAISERROR('Creating procedure [snapshots].[rpt_disk_speed_one_snapshot] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [snapshots].[rpt_disk_queues_one_snapshot] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[rpt_disk_queues_one_snapshot] END GO RAISERROR('Creating procedure [snapshots].[rpt_disk_queues_one_snapshot] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [snapshots].[rpt_disk_ratios_one_snapshot] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[rpt_disk_ratios_one_snapshot] END GO RAISERROR('Creating procedure [snapshots].[rpt_disk_ratios_one_snapshot] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [snapshots].[rpt_disk_usage_per_process] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[rpt_disk_usage_per_process] END GO RAISERROR('Creating procedure [snapshots].[rpt_disk_usage_per_process] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [snapshots].[rpt_waiting_sessions_per_snapshot] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[rpt_waiting_sessions_per_snapshot] END GO RAISERROR('Creating procedure [snapshots].[rpt_waiting_sessions_per_snapshot] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [snapshots].[rpt_memory_usage_per_process] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[rpt_memory_usage_per_process] END GO RAISERROR('Creating procedure [snapshots].[rpt_memory_usage_per_process] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [snapshots].[rpt_wait_stats_by_category_by_snapshot] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[rpt_wait_stats_by_category_by_snapshot] END GO RAISERROR('Creating procedure [snapshots].[rpt_wait_stats_by_category_by_snapshot] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [snapshots].[rpt_cpu_usage] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[rpt_cpu_usage] END GO RAISERROR('Creating procedure [snapshots].[rpt_cpu_usage] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [snapshots].[rpt_mem_usage] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[rpt_mem_usage] END GO RAISERROR('Creating procedure [snapshots].[rpt_mem_usage] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [snapshots].[rpt_io_usage] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[rpt_io_usage] END GO RAISERROR('Creating procedure [snapshots].[rpt_io_usage] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [snapshots].[rpt_network_usage] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[rpt_network_usage] END GO RAISERROR('Creating procedure [snapshots].[rpt_network_usage] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [snapshots].[rpt_wait_stats_one_snapshot] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[rpt_wait_stats_one_snapshot] END GO RAISERROR('Creating procedure [snapshots].[rpt_wait_stats_one_snapshot] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [snapshots].[rpt_server_activity] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[rpt_server_activity] END GO RAISERROR('Creating procedure [snapshots].[rpt_server_activity] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [snapshots].[rpt_wait_stats_one_snapshot_one_category] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[rpt_wait_stats_one_snapshot_one_category] END GO RAISERROR('Creating procedure [snapshots].[rpt_wait_stats_one_snapshot_one_category] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping procedure [snapshots].[rpt_sql_process_memory] ...', 0, 1) WITH NOWAIT; DROP PROCEDURE [snapshots].[rpt_sql_process_memory] END GO RAISERROR('Creating procedure [snapshots].[rpt_sql_process_memory] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping database trigger [add_operator_check] ...', 0, 1) WITH NOWAIT; DROP TRIGGER [add_operator_check] ON DATABASE END GO RAISERROR('Creating database trigger [add_operator_check] ...', 0, 1) WITH NOWAIT; 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 RAISERROR('Dropping database trigger [deny_drop_table] ...', 0, 1) WITH NOWAIT; DROP TRIGGER [deny_drop_table] ON DATABASE END GO RAISERROR('Creating database trigger [deny_drop_table] ...', 0, 1) WITH NOWAIT; 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 /**********************************************************************/ /* install system collector types */ /**********************************************************************/ RAISERROR('', 0, 1) WITH NOWAIT; RAISERROR('Adding collector types...', 0, 1) WITH NOWAIT; 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 */ /**********************************************************************/ RAISERROR('', 0, 1) WITH NOWAIT; RAISERROR('Creating purge data job...', 0, 1) WITH NOWAIT; 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 SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO /**********************************************************************/ /* UTILITY SCHEMAS */ /* Utility objects (in MDW) live in one of the following schemas */ /* [Snapshots] - stores the "live" tables (the tables loaded by DC */ /* collection items */ /* [Sysutility_ucp_staging] - contains the views, SPs, functions */ /* that help to transform the "live" table data into */ /* the "cache" tables (see below) */ /* [sysutility_ucp_core] - contains the cleansed tables, and */ /* views, SPs, and functions that represent the guts */ /* of the Utility data warehouse */ /* [sysutility_ucp_misc] - contains other objects that don't fit */ /* the schemas above. This schema currently contains */ /* only two objects (both functions), and should be */ /* eliminated at some point */ /* */ /* In an ideal world, the UCP tables in the [snapshots] schema would */ /* live in the [sysutility_ucp_staging] schema instead. Current DC */ /* limitations do not allow for that */ /* */ /* The objects in the [snapshots] and [sysutility_ucp_staging] schema */ /* should be entirely private to the MDW database. Only objects in */ /* the [sysutility_ucp_core] schema and the [sysutility_ucp_misc] */ /* schemas should be referenced externally - either by objects in MSDB*/ /* or by Utility object model code */ /**********************************************************************/ RAISERROR('', 0, 1) WITH NOWAIT; BEGIN DECLARE @sql nvarchar(128) -- -- Unfortunately, CREATE SCHEMA needs to be the only statement in its -- batch. That's why we use dynamic sql below -- RAISERROR('Create schema [sysutility_ucp_misc]', 0, 1) WITH NOWAIT; IF (SCHEMA_ID('sysutility_ucp_misc') IS NULL) BEGIN SET @sql = 'CREATE SCHEMA [sysutility_ucp_misc]' EXEC sp_executesql @sql END RAISERROR('Create schema [sysutility_ucp_staging]', 0, 1) WITH NOWAIT; IF (SCHEMA_ID('sysutility_ucp_staging') IS NULL) BEGIN SET @sql = 'CREATE SCHEMA [sysutility_ucp_staging]' EXEC sp_executesql @sql END RAISERROR('Create schema [sysutility_ucp_core]', 0, 1) WITH NOWAIT; IF (SCHEMA_ID('sysutility_ucp_core') IS NULL) BEGIN SET @sql = 'CREATE SCHEMA [sysutility_ucp_core]' EXEC sp_executesql @sql END END GO ----------------------------------------------------------------------- -- View sysutility_ucp_misc.utility_objects_internal -- Categorizes all Utility-owned objects in the MDW database. Used, for -- example, to dynamically discover which tables are cache tables in order -- to update stats on them, or to purge data from all tables that play a -- given role. ----------------------------------------------------------------------- IF OBJECT_ID ('sysutility_ucp_misc.utility_objects_internal') IS NOT NULL BEGIN RAISERROR('Dropping view sysutility_ucp_misc.utility_objects_internal', 0, 1) WITH NOWAIT; DROP VIEW sysutility_ucp_misc.utility_objects_internal; END GO RAISERROR('Creating view sysutility_ucp_misc.utility_objects_internal', 0, 1) WITH NOWAIT; GO CREATE VIEW sysutility_ucp_misc.utility_objects_internal AS SELECT SCHEMA_NAME (obj.[schema_id]) AS [object_schema], obj.name AS [object_name], obj.type_desc AS sql_object_type, CAST (prop.value AS varchar(60)) AS utility_object_type FROM sys.extended_properties AS prop INNER JOIN sys.objects AS obj ON prop.major_id = obj.[object_id] WHERE prop.name = 'MS_UtilityObjectType'; GO EXEC sp_addextendedproperty @name = 'MS_UtilityObjectType', @value = 'MISC', @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_misc', @level1type = 'VIEW', @level1name = 'utility_objects_internal'; GO ----------------------------------------------------------------------- -- Function to get max file size using growth type = KB. Parameters include file_size, max_size, growth size (all in KB) -- The algorithm is: -- the max file size = (max_size - ((max_size - file_size) % growth_size)) -- -- max_size - file_size means the remaining available free space. -- because we grow a fixed chunk specified by growth_size, so we need to figure out -- what would the remainder free space after the maximum growth, and then subtract that -- from max_size. ----------------------------------------------------------------------- IF (OBJECT_ID(N'[sysutility_ucp_misc].[fn_get_max_size_available_by_growth_type_kb]') IS NOT NULL) BEGIN RAISERROR('Dropping function [sysutility_ucp_misc].[fn_get_max_size_available_by_growth_type_kb]', 0, 1) WITH NOWAIT; DROP FUNCTION [sysutility_ucp_misc].[fn_get_max_size_available_by_growth_type_kb] END GO RAISERROR('Creating function [sysutility_ucp_misc].[fn_get_max_size_available_by_growth_type_kb]', 0, 1) WITH NOWAIT; GO CREATE FUNCTION [sysutility_ucp_misc].[fn_get_max_size_available_by_growth_type_kb]( @file_size_kb REAL, @max_size_kb REAL, @growth_size_kb REAL) RETURNS REAL AS BEGIN DECLARE @max_size_available_kb REAL; SELECT @max_size_available_kb = @file_size_kb; IF (@growth_size_kb > 0 AND @max_size_kb > @file_size_kb) BEGIN SELECT @max_size_available_kb = (@max_size_kb - CONVERT(REAL, CONVERT(BIGINT, @max_size_kb - @file_size_kb) % CONVERT(BIGINT, @growth_size_kb))) END RETURN @max_size_available_kb END GO ----------------------------------------------------------------------- -- Function to get max file size using growth type = percentage. Parameters include file_size, max_size (in KB) and percentage -- The algorithm is: -- exp = log( (max_size / file_size) / (1 + growth_percent / 100) ) -- the max file size = file_size * (1 + growth_percent / 100) ^ exp ----------------------------------------------------------------------- IF (OBJECT_ID(N'[sysutility_ucp_misc].[fn_get_max_size_available_by_growth_type_percent]') IS NOT NULL) BEGIN RAISERROR('Dropping function [sysutility_ucp_misc].[fn_get_max_size_available_by_growth_type_percent]', 0, 1) WITH NOWAIT; DROP FUNCTION [sysutility_ucp_misc].[fn_get_max_size_available_by_growth_type_percent] END GO RAISERROR('Creating function [sysutility_ucp_misc].[fn_get_max_size_available_by_growth_type_percent]', 0, 1) WITH NOWAIT; GO CREATE FUNCTION [sysutility_ucp_misc].[fn_get_max_size_available_by_growth_type_percent]( @file_size_kb REAL, @max_size_kb REAL, @growth_percent REAL) RETURNS REAL AS BEGIN DECLARE @max_size_available_kb REAL; DECLARE @one_plus_growth_percent REAL; DECLARE @exponent REAL; SELECT @max_size_available_kb = @file_size_kb; SELECT @one_plus_growth_percent = 1 + @growth_percent / 100; --- @file_size_kb > 0 is added to avoid the divided by zero exception. When a database is in the Emergency state, the size --- of its log file is zero. IF (@growth_percent > 0 AND @max_size_kb > @file_size_kb AND @file_size_kb > 0) BEGIN SELECT @exponent = FLOOR(LOG10(@max_size_kb / @file_size_kb) / LOG10(@one_plus_growth_percent)); SELECT @max_size_available_kb = @file_size_kb * POWER(@one_plus_growth_percent, @exponent); END RETURN @max_size_available_kb END GO ----------------------------------------------------------------------- -- Function to get max size available depending on the file_size, its mx_size, growth and free_space_on_drive (everything in KB). -- Algorithm used for calculating max size available on the drive: -- -- if no auto grow then return the current file size, done. -- -- assuming auto grow now: -- if max size is configured and it's less than the (current file size + volume free space) -- which means the max size boundary is in effect, then we use the max_size as the boundary. -- if max size is not configured (unlimited growth) or it's already bigger than the remaining size, -- which means the max size boundary is NOT in effect, then we use the (curent file size + volume free space) -- as the boundary. -- -- now we got the boundary size, we need to take a look the growth type: -- if growth type = kb (0), then we simply try to fit as many chunks as possible (need to use % operation here) -- if growth type = percent (1), then it's a little tricky, we need to find the max exp where -- (current file size) * (1 + growth percent) ^ exp <= boundary size. -- log/power operations involved. one we figure out the exp, we can compute the max available size. -- -- NOTE: the return value is -- (the current file size + the max possible additional space the file can grow into) ----------------------------------------------------------------------- IF (OBJECT_ID(N'[sysutility_ucp_misc].[fn_get_max_size_available]') IS NOT NULL) BEGIN RAISERROR('Dropping function [sysutility_ucp_misc].[fn_get_max_size_available]', 0, 1) WITH NOWAIT; DROP FUNCTION [sysutility_ucp_misc].[fn_get_max_size_available] END GO RAISERROR('Creating function [sysutility_ucp_misc].[fn_get_max_size_available]', 0, 1) WITH NOWAIT; GO CREATE FUNCTION [sysutility_ucp_misc].[fn_get_max_size_available]( @file_size_kb REAL, @max_size_kb REAL, @growth REAL, -- growth is KB when type is not percentage, or a whole number percentage when percentage @smo_growth_type SMALLINT, -- @smo_growth_type == 0 is KB growth, == 1 means percentage, or == 99 not supported to grow @free_space_on_drive_kb BIGINT) RETURNS REAL AS BEGIN DECLARE @max_size_available_kb REAL; DECLARE @projected_max_file_size_kb REAL; -- Be conservative and initialize total space to @file_size_kb (assume no autogrow) SELECT @max_size_available_kb = @file_size_kb; -- Let projected size be the current file size + volume free space (assuming no one else is competing and its completely available for this file) SELECT @projected_max_file_size_kb = @file_size_kb + @free_space_on_drive_kb; -- No auto grow, return the configured file size IF (@smo_growth_type = 99) BEGIN SELECT @max_size_available_kb = @file_size_kb; END ELSE BEGIN IF (0 < @max_size_kb AND @max_size_kb < @projected_max_file_size_kb) BEGIN -- if maxsize is configured and it's less than the project space -- then we use the maxsize as the growth boundary. SELECT @max_size_available_kb = CASE WHEN (@smo_growth_type = 1) -- percent growth THEN sysutility_ucp_misc.fn_get_max_size_available_by_growth_type_percent(@file_size_kb, @max_size_kb, @growth) WHEN (@smo_growth_type = 0) -- KB growth THEN sysutility_ucp_misc.fn_get_max_size_available_by_growth_type_kb(@file_size_kb, @max_size_kb, @growth) ELSE @max_size_kb END END ELSE BEGIN -- either maxsize is not configured, in this case we use the project space -- or maxsize is bigger than the project space, and we suse the project space as well. SELECT @max_size_available_kb = CASE WHEN (@smo_growth_type = 1) -- percent growth THEN sysutility_ucp_misc.fn_get_max_size_available_by_growth_type_percent(@file_size_kb, @projected_max_file_size_kb, @growth) WHEN (@smo_growth_type = 0) -- KB growth THEN sysutility_ucp_misc.fn_get_max_size_available_by_growth_type_kb(@file_size_kb, @projected_max_file_size_kb, @growth) ELSE @projected_max_file_size_kb END END END -- VSTS 351411 -- In SQL2008 and SQL2008 R2, unfortunately the support for file stream file -- in SMO and dmv aren't complete: it always return 0 for everything including -- @file_size_kb, @max_size_kb, growth, thus walking through the code path above, -- we'll return 0, then the our data file storage utilization property use this -- value (as the denominator) to compute the percentage which would result -- in divide by zero (DBZ). -- -- So for that case, we simply return the @projected_max_file_size_kb to avoid DBZ. Of course -- there is a tiny chance @projected_max_file_size_kb is also 0 (due to volume free space is 0 -- the volume is full!) so we'll simply return 1 (kb) -- -- Note 1: to avoid comparing equality of double-typed variable to 0, check against 1 KB -- it wouldn't be any faster but readability is lower. IF (@max_size_available_kb < 1.0) BEGIN SELECT @max_size_available_kb = @projected_max_file_size_kb; -- what if @projected_max_file_size_kb is still 0 (or near 0)? use 1 kb. IF (@max_size_available_kb < 1.0) BEGIN SELECT @max_size_available_kb = 1.0; END END RETURN @max_size_available_kb; END GO EXEC sp_addextendedproperty @name = 'MS_UtilityObjectType', @value = 'MISC', @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_misc', @level1type = 'FUNCTION', @level1name = 'fn_get_max_size_available'; GO ------------------------------------------------------------------------- -- Create table: sysutility_ucp_batch_manifests_internal -- This is the target table to store the manifest information for every batch collected -- The purpose of the manifest is to qualify the integrity of the data uploaded on the UCP. -- The batch manifest primarily includes: -- 1. server_instance_name: the server\instance name of the MI -- 2. xx_row_count: row count for each of the table collected/uploaded by the utility T-SQL collection item query -- 3. batch_time: the batch creation date-time stamp -- -- Note: the manifest info is stored in the form of name/value pair. This pattern is -- used is to minimize structural changes to the table in case there are collection -- queries added/removed in future that affects the manifest. ------------------------------------------------------------------------- IF NOT EXISTS (SELECT name FROM sys.objects WHERE object_id = OBJECT_ID(N'[snapshots].[sysutility_ucp_batch_manifests_internal]') ) BEGIN RAISERROR('Creating table [snapshots].[sysutility_ucp_batch_manifests_internal]', 0, 1) WITH NOWAIT; CREATE TABLE [snapshots].[sysutility_ucp_batch_manifests_internal] ( [server_instance_name] SYSNAME NOT NULL, [batch_time] DATETIMEOFFSET(7) NOT NULL, [parameter_name] SYSNAME NOT NULL, -- Name, representing the collection query uploading data to the live table [parameter_value] SQL_VARIANT NULL, -- Value, indicating the number of rows collected/uploaded by the respective query [collection_time] DATETIMEOFFSET(7) NULL, [snapshot_id] INT NULL ) ON [PRIMARY] ALTER TABLE [snapshots].[sysutility_ucp_batch_manifests_internal] WITH CHECK ADD CONSTRAINT [FK_sysutility_ucp_batch_manifests_internal] FOREIGN KEY([snapshot_id]) REFERENCES [core].[snapshots_internal] ([snapshot_id]) ON DELETE CASCADE ALTER TABLE [snapshots].[sysutility_ucp_batch_manifests_internal] CHECK CONSTRAINT [FK_sysutility_ucp_batch_manifests_internal] ALTER TABLE [snapshots].[sysutility_ucp_batch_manifests_internal] WITH CHECK ADD CONSTRAINT [CHK_check_operator_E4F8A95D-2C44-48B6-85BA-E78E47C7ACCE] CHECK (([core].[fn_check_operator]([snapshot_id])=(1))) ALTER TABLE [snapshots].[sysutility_ucp_batch_manifests_internal] CHECK CONSTRAINT [CHK_check_operator_E4F8A95D-2C44-48B6-85BA-E78E47C7ACCE] -- This index is used by the caching job to identify the latest consistent batches -- This index is also used by the DC purge job to seek against snapshot_id CREATE CLUSTERED INDEX CI_sysutility_ucp_batch_manifests_internal ON [snapshots].[sysutility_ucp_batch_manifests_internal](snapshot_id) EXEC sp_addextendedproperty @name = 'MS_UtilityObjectType', @value = 'LIVE', @level0type = 'SCHEMA', @level0name = 'snapshots', @level1type = 'TABLE', @level1name = 'sysutility_ucp_batch_manifests_internal'; END GO --********************************************************************* -- Create table: consistent_batch_manifests_internal -- This table stores the consistent batch information for the enrolled MI's. -- The data returned by the view consistent_batch_manifests is stored -- in this table and is consumed by the caching job step. --********************************************************************* IF (OBJECT_ID(N'[sysutility_ucp_staging].[consistent_batch_manifests_internal]', 'U') IS NULL) BEGIN RAISERROR('Creating [sysutility_ucp_staging].[consistent_batch_manifests_internal] table', 0, 1) WITH NOWAIT; CREATE TABLE [sysutility_ucp_staging].[consistent_batch_manifests_internal] ( [server_instance_name] SYSNAME NOT NULL, [batch_time] DATETIMEOFFSET NOT NULL CONSTRAINT PK_consistent_batch_manifests_internal PRIMARY KEY CLUSTERED (server_instance_name, batch_time) ) EXEC sp_addextendedproperty @name = 'MS_UtilityObjectType', @value = 'STAGING', @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_staging', @level1type = 'TABLE', @level1name = 'consistent_batch_manifests_internal'; END; GO ---------------------------------------------------------------------------- -- Table [sysutility_ucp_dac_collected_execution_statistics_internal] -- Stores the output of sp_sysutility_instance_retrieve_dac_execution_statistics, -- which is executed by the Utility Data Collector collection set to retrieve DAC -- info, including execution statistics like the amount of CPU time being consumed -- by each DAC. The execution statistics (%CPU) returned by this query are -- actually collected by a different SQL Agent job that runs every 15 seconds on -- the managed instance. ---------------------------------------------------------------------------- IF (OBJECT_ID(N'[snapshots].[sysutility_ucp_dac_collected_execution_statistics_internal]', 'U') IS NULL) BEGIN RAISERROR('Creating [snapshots].[sysutility_ucp_dac_collected_execution_statistics_internal] table', 0, 1) WITH NOWAIT; CREATE TABLE [snapshots].[sysutility_ucp_dac_collected_execution_statistics_internal] ( [physical_server_name] SYSNAME, [server_instance_name] SYSNAME, [dac_db] SYSNAME, [dac_deploy_date] DATETIME, [dac_description] NVARCHAR(4000) NULL, [dac_name] SYSNAME, [interval_start_time] DATETIMEOFFSET NULL,-- The first every-15-second DAC CPU collection time included in this row's time interval [interval_end_time] DATETIMEOFFSET NULL, -- The last every-15-second DAC CPU collection time included in this row's time interval (typically ~15 min after interval_start_time) [interval_cpu_time_ms] BIGINT NULL, -- The total amount of CPU time consumed by this DAC in the time interval [interval_cpu_pct] REAL NULL, -- The average %CPU utilization for this DAC in the time interval (% is of all available CPU cycles, not just a single CPU) [lifetime_cpu_time_ms] BIGINT NULL, -- The cumulative total CPU time consumed by this DAC since we started monitoring it [batch_time] datetimeoffset(7), -- Start time for one execution of the Utility data collection job [collection_time] [datetimeoffset](7), -- The execution time of the DC query that gathered this data from the managed instance [snapshot_id] [int], -- This index is used by the caching job to copy the latest consistent batch from live to cache table CONSTRAINT PK_sysutility_ucp_dac_collected_execution_statistics_internal PRIMARY KEY CLUSTERED (server_instance_name, batch_time, dac_name) ); -- This index is used by the DC purge job to seek against snapshot_id CREATE NONCLUSTERED INDEX NCI_sysutility_ucp_dac_collected_execution_statistics_internal ON [snapshots].[sysutility_ucp_dac_collected_execution_statistics_internal](snapshot_id) -- Create a foreign key referencing snapshots_internal so that rows in this table will be deleted by the DC purge job ALTER TABLE [snapshots].[sysutility_ucp_dac_collected_execution_statistics_internal] WITH CHECK ADD CONSTRAINT [fk_dac_collected_execution_statistics_internal_snapshots_internal] FOREIGN KEY([snapshot_id]) REFERENCES [core].[snapshots_internal] ([snapshot_id]) ON DELETE CASCADE; EXEC sp_addextendedproperty @name = 'MS_UtilityObjectType', @value = 'LIVE', @level0type = 'SCHEMA', @level0name = 'snapshots', @level1type = 'TABLE', @level1name = 'sysutility_ucp_dac_collected_execution_statistics_internal'; END; GO ---------------------------------------------------------------------------------- -- View latest_dac_cpu_utilization -- Gets the latest DAC-related information and CPU utilization for each DAC on a -- managed instanced ---------------------------------------------------------------------------------- IF EXISTS (SELECT name FROM sys.views WHERE object_id = OBJECT_ID(N'[sysutility_ucp_staging].[latest_dac_cpu_utilization]')) BEGIN RAISERROR('Dropping [sysutility_ucp_staging].[latest_dac_cpu_utilization] view', 0, 1) WITH NOWAIT; DROP VIEW [sysutility_ucp_staging].[latest_dac_cpu_utilization] END GO RAISERROR('Creating [sysutility_ucp_staging].[latest_dac_cpu_utilization] view', 0, 1) WITH NOWAIT; GO CREATE VIEW [sysutility_ucp_staging].[latest_dac_cpu_utilization] AS SELECT physical_server_name, ds.server_instance_name, dac_db, dac_deploy_date, dac_description, dac_name, lifetime_cpu_time_ms, interval_cpu_pct AS latest_cpu_pct, interval_cpu_time_ms AS latest_interval_cpu_time_ms, interval_start_time AS latest_interval_start_time, interval_end_time AS latest_interval_end_time, ds.batch_time, N'Utility[@Name=''' + CONVERT(SYSNAME, SERVERPROPERTY(N'ServerName')) + N''']/DeployedDac[@Name=''' + ds.dac_name + N''' and @ServerInstanceName=''' + ds.server_instance_name + N''']' AS urn, N'SQLSERVER:\Utility\'+CASE WHEN 0 = CHARINDEX(N'\', CONVERT(SYSNAME, SERVERPROPERTY(N'ServerName')), 1) THEN CONVERT(SYSNAME, SERVERPROPERTY(N'ServerName')) + N'\DEFAULT' ELSE CONVERT(SYSNAME, SERVERPROPERTY(N'ServerName')) END +N'\DeployedDacs\'+msdb.dbo.fn_encode_sqlname_for_powershell(ds.dac_name+'.'+ds.server_instance_name) AS powershell_path FROM [snapshots].[sysutility_ucp_dac_collected_execution_statistics_internal] ds INNER JOIN [sysutility_ucp_staging].[consistent_batch_manifests_internal] cb ON ds.server_instance_name = cb.server_instance_name AND ds.batch_time = cb.batch_time GO EXEC sp_addextendedproperty @name = 'MS_UtilityObjectType', @value = 'STAGING', @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_staging', @level1type = 'VIEW', @level1name = 'latest_dac_cpu_utilization'; GO ---------------------------------------------------------------------------- --- MDW Table for storing SMO server collection set information. ---------------------------------------------------------------------------- IF NOT EXISTS (SELECT name FROM .[sys].[objects] WHERE object_id = OBJECT_ID(N'[snapshots].[sysutility_ucp_smo_properties_internal]')) BEGIN RAISERROR('Creating [snapshots].[sysutility_ucp_smo_properties_internal] table', 0, 1) WITH NOWAIT; CREATE TABLE [snapshots].[sysutility_ucp_smo_properties_internal] ( [physical_server_name] SYSNAME, [server_instance_name] SYSNAME, [object_type] INT, [urn] NVARCHAR(4000), [property_name] NVARCHAR(128), [property_value] SQL_VARIANT, [batch_time] DATETIMEOFFSET(7), -- Start time for one execution of the Utility data collection job [collection_time] DATETIMEOFFSET(7) NULL, [snapshot_id] INT NULL, -- NOTE: compression is enabled on this table at runtime (during Create UCP) in sp_initialize_mdw_internal ) -- This index is used by the caching job to copy the latest consistent batch from live to cache table CREATE CLUSTERED INDEX CI_sysutility_ucp_smo_properties_internal ON [snapshots].[sysutility_ucp_smo_properties_internal](server_instance_name, batch_time); -- This index is used by the DC purge job to seek against snapshot_id CREATE NONCLUSTERED INDEX NCI_sysutility_ucp_smo_properties_internal ON [snapshots].[sysutility_ucp_smo_properties_internal](snapshot_id) -- Create a foreign key referencing snapshots_internal so that rows in this table will be deleted by the DC purge job ALTER TABLE [snapshots].[sysutility_ucp_smo_properties_internal] WITH CHECK ADD CONSTRAINT [FK_sysutility_ucp_smo_properties_internal_snapshots_internal] FOREIGN KEY([snapshot_id]) REFERENCES [core].[snapshots_internal] ([snapshot_id]) ON DELETE CASCADE; EXEC sp_addextendedproperty @name = 'MS_UtilityObjectType', @value = 'LIVE', @level0type = 'SCHEMA', @level0name = 'snapshots', @level1type = 'TABLE', @level1name = 'sysutility_ucp_smo_properties_internal'; END GO ------------------------------------------------------------------------------ -- SQL Server View to read latest snapshot data for SMO properties ------------------------------------------------------------------------------ IF EXISTS (SELECT name FROM sys.views WHERE object_id = OBJECT_ID(N'[sysutility_ucp_staging].[latest_smo_properties]')) BEGIN RAISERROR('Dropping [sysutility_ucp_staging].[latest_smo_properties] view', 0, 1) WITH NOWAIT; DROP VIEW [sysutility_ucp_staging].[latest_smo_properties] END GO RAISERROR('Creating [sysutility_ucp_staging].[latest_smo_properties] view', 0, 1) WITH NOWAIT; GO CREATE VIEW [sysutility_ucp_staging].[latest_smo_properties] AS SELECT s.physical_server_name, s.server_instance_name, s.urn, s.object_type, s.property_name,s.property_value, s.snapshot_id, s.batch_time FROM [snapshots].[sysutility_ucp_smo_properties_internal] AS s INNER JOIN [sysutility_ucp_staging].[consistent_batch_manifests_internal] cb ON s.server_instance_name = cb.server_instance_name AND s.batch_time = cb.batch_time GO EXEC sp_addextendedproperty @name = 'MS_UtilityObjectType', @value = 'STAGING', @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_staging', @level1type = 'VIEW', @level1name = 'latest_smo_properties'; GO ----------------------------------------------------------------------- -- Created [snapshots].[sysutility_ucp_cpu_memory_configurations_internal] and [snapshots].[sysutility_ucp_cpu_affinity_internal] tables if they are not existing. ----------------------------------------------------------------------- IF NOT EXISTS (SELECT name FROM sys.objects WHERE object_id = OBJECT_ID(N'[snapshots].[sysutility_ucp_cpu_memory_configurations_internal]')) BEGIN RAISERROR('Creating table [snapshots].[sysutility_ucp_cpu_memory_configurations_internal]', 0, 1) WITH NOWAIT; CREATE TABLE [snapshots].[sysutility_ucp_cpu_memory_configurations_internal]( [server_instance_name] SYSNAME NOT NULL, [is_clustered_server] SMALLINT NULL, [virtual_server_name] SYSNAME, [physical_server_name] SYSNAME NOT NULL, [num_processors] INT NULL, [server_processor_usage] REAL, [instance_processor_usage] REAL, [cpu_name] NVARCHAR(128) NULL, [cpu_caption] NVARCHAR(128) NULL, [cpu_family] NVARCHAR(128) NULL, [cpu_architecture] NVARCHAR(64) NULL, [cpu_max_clock_speed] DECIMAL(10,0) NULL, -- this is in MHz (Mega Hertz) [cpu_clock_speed] DECIMAL(10,0) NULL, [l2_cache_size] DECIMAL(10,0) NULL, [l3_cache_size] DECIMAL(10,0) NULL, [batch_time] [datetimeoffset](7), -- Start time for one execution of the Utility data collection job [collection_time] [datetimeoffset](7) NULL, [snapshot_id] [int] NULL -- This index is used by the caching job to copy the latest consistent batch from live to cache table CONSTRAINT PK_sysutility_cpu_memory_related_info_internal_clustered PRIMARY KEY CLUSTERED ([server_instance_name], [batch_time], [physical_server_name]) ) ON [PRIMARY]; -- This index is used by the DC purge job to seek against snapshot_id CREATE NONCLUSTERED INDEX NCI_sysutility_ucp_cpu_memory_configurations_internal ON [snapshots].[sysutility_ucp_cpu_memory_configurations_internal](snapshot_id) ALTER TABLE [snapshots].[sysutility_ucp_cpu_memory_configurations_internal] WITH CHECK ADD CONSTRAINT [FK_sysutility_cpu_memory_related_info_snapshots_internal_snapshots_internal] FOREIGN KEY([snapshot_id]) REFERENCES [core].[snapshots_internal] ([snapshot_id]) ON DELETE CASCADE; ALTER TABLE [snapshots].[sysutility_ucp_cpu_memory_configurations_internal] CHECK CONSTRAINT [FK_sysutility_cpu_memory_related_info_snapshots_internal_snapshots_internal]; ALTER TABLE [snapshots].[sysutility_ucp_cpu_memory_configurations_internal] WITH CHECK ADD CONSTRAINT [CHK_check_operator_9DAA9ACC-F1E1-44F8-8B74-D081734E5F39] CHECK (([core].[fn_check_operator]([snapshot_id])=(1))); ALTER TABLE [snapshots].[sysutility_ucp_cpu_memory_configurations_internal] CHECK CONSTRAINT [CHK_check_operator_9DAA9ACC-F1E1-44F8-8B74-D081734E5F39]; EXEC sp_addextendedproperty @name = 'MS_UtilityObjectType', @value = 'LIVE', @level0type = 'SCHEMA', @level0name = 'snapshots', @level1type = 'TABLE', @level1name = 'sysutility_ucp_cpu_memory_configurations_internal'; END GO ----------------------------------------------------------------------- -- Gets the latest CPU/memory configurations for each computer - along -- with the current CPU utilization for the computer. -- -- NOTE: If there are multiple SQL instances (MIs) on a computer, each of -- them uploads its snapshot of information about the computer. We -- only want one entry for the computer - so we pick the entry that -- was uploaded last ----------------------------------------------------------------------- IF EXISTS (SELECT name FROM sys.views WHERE object_id = OBJECT_ID(N'[sysutility_ucp_staging].[latest_computer_cpu_memory_configuration]')) BEGIN RAISERROR('Dropping [sysutility_ucp_staging].[latest_computer_cpu_memory_configuration] view', 0, 1) WITH NOWAIT; DROP VIEW [sysutility_ucp_staging].[latest_computer_cpu_memory_configuration] END GO RAISERROR('Creating view [sysutility_ucp_staging].[latest_computer_cpu_memory_configuration]', 0, 1) WITH NOWAIT; GO CREATE VIEW [sysutility_ucp_staging].[latest_computer_cpu_memory_configuration] AS SELECT * FROM ( SELECT ROW_NUMBER() OVER (PARTITION BY t.physical_server_name ORDER BY t.batch_time DESC) AS Rank, [virtual_server_name], [is_clustered_server],[physical_server_name], [num_processors], [cpu_name], [cpu_caption], [cpu_family], [cpu_architecture], [cpu_max_clock_speed], [cpu_clock_speed], [l2_cache_size], [l3_cache_size], [server_processor_usage], t.[batch_time], N'Utility[@Name=''' + CONVERT(SYSNAME, SERVERPROPERTY(N'ServerName')) + N''']/Computer[@Name=''' + t.physical_server_name + N''']' AS urn, N'SQLSERVER:\Utility\'+CASE WHEN 0 = CHARINDEX(N'\', CONVERT(SYSNAME, SERVERPROPERTY(N'ServerName')), 1) THEN CONVERT(SYSNAME, SERVERPROPERTY(N'ServerName')) + N'\DEFAULT' ELSE @@SERVERNAME END +N'\Computers\'+msdb.dbo.fn_encode_sqlname_for_powershell(t.physical_server_name) AS powershell_path FROM [snapshots].[sysutility_ucp_cpu_memory_configurations_internal] AS t INNER JOIN [sysutility_ucp_staging].[consistent_batch_manifests_internal] cb ON t.server_instance_name = cb.server_instance_name AND t.batch_time = cb.batch_time ) AS S WHERE S.Rank = 1 GO EXEC sp_addextendedproperty @name = 'MS_UtilityObjectType', @value = 'STAGING', @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_staging', @level1type = 'VIEW', @level1name = 'latest_computer_cpu_memory_configuration'; GO ----------------------------------------------------------------------- ----------------------------------------------------------------------- IF EXISTS (SELECT name FROM sys.views WHERE object_id = OBJECT_ID(N'[sysutility_ucp_staging].[latest_instance_cpu_utilization]')) BEGIN RAISERROR('Dropping [sysutility_ucp_staging].[latest_instance_cpu_utilization] view', 0, 1) WITH NOWAIT; DROP VIEW [sysutility_ucp_staging].[latest_instance_cpu_utilization] END GO RAISERROR('Creating view [sysutility_ucp_staging].[latest_instance_cpu_utilization]', 0, 1) WITH NOWAIT; GO CREATE VIEW [sysutility_ucp_staging].[latest_instance_cpu_utilization] AS SELECT t.[server_instance_name], [instance_processor_usage], t.[batch_time] FROM [snapshots].[sysutility_ucp_cpu_memory_configurations_internal] AS t INNER JOIN [sysutility_ucp_staging].[consistent_batch_manifests_internal] cb ON t.server_instance_name = cb.server_instance_name AND t.batch_time = cb.batch_time GO EXEC sp_addextendedproperty @name = 'MS_UtilityObjectType', @value = 'STAGING', @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_staging', @level1type = 'VIEW', @level1name = 'latest_instance_cpu_utilization'; GO ----------------------------------------------------------------------- -- Created [snapshots].[sysutility_ucp_volumes_internal] tables to collect volume-related -- information from the MIs ----------------------------------------------------------------------- IF NOT EXISTS (SELECT name FROM sys.objects WHERE object_id = OBJECT_ID(N'[snapshots].[sysutility_ucp_volumes_internal]') ) BEGIN RAISERROR('Creating table [snapshots].[sysutility_ucp_volumes_internal]', 0, 1) WITH NOWAIT; CREATE TABLE [snapshots].[sysutility_ucp_volumes_internal]( [server_instance_name] SYSNAME, [virtual_server_name] SYSNAME, [physical_server_name] SYSNAME, [volume_device_id] SYSNAME NOT NULL, [volume_name] [nvarchar](260) NOT NULL, [total_space_available] [real] NULL, -- in MB [free_space] [real] NULL, -- in MB [batch_time] datetimeoffset(7), -- Start time for one execution of the Utility data collection job [collection_time] datetimeoffset(7) NULL, [snapshot_id] [int] NULL -- This index is used by the caching job to copy the latest consistent batch from live to cache table CONSTRAINT PK_sysutility_volumes_info_internal PRIMARY KEY CLUSTERED (server_instance_name, batch_time, volume_device_id) ) ON [PRIMARY] -- This index is used by the DC purge job to seek against snapshot_id CREATE NONCLUSTERED INDEX NCI_sysutility_ucp_volumes_internal ON [snapshots].[sysutility_ucp_volumes_internal](snapshot_id) ALTER TABLE [snapshots].[sysutility_ucp_volumes_internal] WITH CHECK ADD CONSTRAINT [FK_volumes_info_snapshots_internal] FOREIGN KEY([snapshot_id]) REFERENCES [core].[snapshots_internal] ([snapshot_id]) ON DELETE CASCADE ALTER TABLE [snapshots].[sysutility_ucp_volumes_internal] CHECK CONSTRAINT [FK_volumes_info_snapshots_internal] ALTER TABLE [snapshots].[sysutility_ucp_volumes_internal] WITH CHECK ADD CONSTRAINT [CHK_check_operator_D79F8519-D243-4176-8291-6F3BA8EF776D] CHECK (([core].[fn_check_operator]([snapshot_id])=(1))) ALTER TABLE [snapshots].[sysutility_ucp_volumes_internal] CHECK CONSTRAINT [CHK_check_operator_D79F8519-D243-4176-8291-6F3BA8EF776D] EXEC sp_addextendedproperty @name = 'MS_UtilityObjectType', @value = 'LIVE', @level0type = 'SCHEMA', @level0name = 'snapshots', @level1type = 'TABLE', @level1name = 'sysutility_ucp_volumes_internal'; END GO ----------------------------------------------------------------------- -- View to get latest volumes information. -- NOTE: If you change the output of this view in any way, be sure to also update the -- corresponding "stub" object in instmsdb.sql. -- -- NOTE: When you have multiple SQL instances on a machine, each of them is uploading -- volume information (as it sees it). However, we only want one entry for each -- volume on the computer. What we do here is to simply pick the entry from the -- instance that uploaded last - hence the partition-by (physical_server_name,volume_device_id) -- The "latest_computers" view exhibits very similar behavior. ----------------------------------------------------------------------- IF EXISTS (SELECT name FROM sys.views WHERE object_id = OBJECT_ID(N'[sysutility_ucp_staging].[latest_volumes]')) BEGIN RAISERROR('Dropping view [sysutility_ucp_staging].[latest_volumes]', 0, 1) WITH NOWAIT; DROP VIEW [sysutility_ucp_staging].[latest_volumes] END GO RAISERROR('Creating view [sysutility_ucp_staging].[latest_volumes]', 0, 1) WITH NOWAIT; GO CREATE VIEW [sysutility_ucp_staging].[latest_volumes] AS SELECT [virtual_server_name], [physical_server_name], [volume_device_id], [volume_name], [total_space_available], -- in MB [free_space], -- in MB N'SQLSERVER:\Utility\'+ CASE WHEN 0 = CHARINDEX(N'\', CONVERT(SYSNAME, SERVERPROPERTY(N'ServerName')), 1) THEN CONVERT(SYSNAME, SERVERPROPERTY(N'ServerName')) + N'\DEFAULT' ELSE CONVERT(SYSNAME, SERVERPROPERTY(N'ServerName')) END +N'\Computers\'+msdb.dbo.fn_encode_sqlname_for_powershell(physical_server_name) +N'\Volumes\'+msdb.dbo.fn_encode_sqlname_for_powershell(volume_name) AS powershell_path, [batch_time], [snapshot_id] FROM ( SELECT [virtual_server_name], [physical_server_name], [volume_device_id], [volume_name], [total_space_available], [free_space], V.[batch_time], [snapshot_id], ROW_NUMBER() OVER (PARTITION BY physical_server_name,volume_device_id ORDER BY V.batch_time DESC) rk FROM [snapshots].[sysutility_ucp_volumes_internal] AS V INNER JOIN [sysutility_ucp_staging].[consistent_batch_manifests_internal] cb ON V.server_instance_name = cb.server_instance_name AND V.batch_time = cb.batch_time ) AS T WHERE T.rk = 1 GO EXEC sp_addextendedproperty @name = 'MS_UtilityObjectType', @value = 'STAGING', @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_staging', @level1type = 'VIEW', @level1name = 'latest_volumes'; GO --********************************************************************* -- Create procedure: sp_get_consistent_batches -- This procedure gets the consistent batch information for the enrolled MI's -- and stores it into the consistent_batch_manifests_internal table. -- This SP is executed by the caching job to identify the consistent batches -- whose data needs to be copied from the live to cache tables for every run. -- -- A batch is marked consistent if the number of records in each table uploaded -- match to the respective row count in the batch manifest. The evaluation of -- last batch (a) and second-last batch (b) for a given MI is based on following rules: -- . Both a and b exists and are consistent: -- Most recent batch (a) is returned -- . Either a or b exists -- Returns a or b only if it is consistent. Possiblity of delayed upload from MI side. -- . Both a and b exists and are inconsistent: -- No batch info is returned for that MI. Possiblity of failure in collection/upload on MI side. -- . Both a and b does not exist -- No batch info is not returned for that MI. Aged-out data is currently not considered, VSTS #319498 --********************************************************************* IF OBJECT_ID ('[sysutility_ucp_staging].[sp_get_consistent_batches]') IS NOT NULL BEGIN RAISERROR ('Dropping procedure [sysutility_ucp_staging].[sp_get_consistent_batches]', 0, 1) WITH NOWAIT; DROP PROCEDURE [sysutility_ucp_staging].[sp_get_consistent_batches] END; GO RAISERROR ('Creating procedure [sysutility_ucp_staging].[sp_get_consistent_batches]', 0, 1) WITH NOWAIT; GO CREATE PROCEDURE [sysutility_ucp_staging].[sp_get_consistent_batches] AS BEGIN SET NOCOUNT ON; -- Note: As we are not currently caching the aged-out data, this SP -- clears the existing records and inserts the new data. However, as a fix for -- VSTS #319498 (display aged-out data) this behavior needs to be changed -- to UPSERT for any existing or new data and delete the entries whose -- data is purged (> 7 days). -- Get the manifest information for the latest uploaded batches. The "manifest" info includes -- the expected number of rows that should have been uploaded into each live table for the -- batch. This query captures the manifest for most recent unprocessed batch (T) and the -- immediately prior (T-1) unprocessed batch from each managed instance since the last execution -- of the sp_copy_live_table_data_into_cache_tables stored proc. -- -- This rowset is staged in a temp table b/c the query optimizer cannot accurately predict the -- number of rows that qualify for "WHERE bm.snapshot_id > sp.latest_consistent_snapshot_id". -- -- Note: This view may fetch the last 2 batch manifest rows for a given MI. The reason for -- considering two batches is to use the latest one that is consistent. If the latest one is -- missing (delayed upload) or inconsistent (failed or still-in-progress upload), we will use -- the second-to-last batch, assuming that it is consistent. This makes the caching job -- resilient to occasional delays in the MI upload job. SELECT server_instance_name , batch_time , CONVERT(INT, dac_packages_row_count) AS dac_packages_row_count , CONVERT(INT, cpu_memory_configurations_row_count) AS cpu_memory_configurations_row_count , CONVERT(INT, volumes_row_count) AS volumes_row_count , CONVERT(INT, smo_properties_row_count) AS smo_properties_row_count INTO #batch_manifests_latest FROM (SELECT bm.server_instance_name, bm.batch_time, bm.parameter_name, bm.parameter_value FROM snapshots.sysutility_ucp_batch_manifests_internal bm , msdb.dbo.sysutility_ucp_snapshot_partitions_internal AS sp WHERE bm.snapshot_id > sp.latest_consistent_snapshot_id -- The [time_id] = 1 partition gives us the max snapshot_id the last time that the -- sp_copy_live_table_data_into_cache_tables proc was executed (previous high water -- mark). We will consider for processing any snapshots that have been uploaded -- since then. AND sp.time_id = 1) AS lbm PIVOT (MAX(parameter_value) FOR parameter_name IN (dac_packages_row_count , cpu_memory_configurations_row_count , volumes_row_count , smo_properties_row_count)) pvt; -- Truncate the table TRUNCATE TABLE [sysutility_ucp_staging].[consistent_batch_manifests_internal]; -- Get the set of latest batches that are consistent with respect to the data uploaded to -- each live table. A check is made to verify that the number of rows uploaded matches the -- expected row count in that batch's manifest. -- -- These rowsets are staged in temp tables b/c the query optimizer cannot accurately predict -- the number of rows that qualify for "HAVING COUNT(*) = bm.cpu_memory_configurations_row_count". SELECT bm.server_instance_name, bm.batch_time INTO #dac_statistics_consistent_batches FROM #batch_manifests_latest bm -- Note: No records in DAC table doesn't mean issue with upload -- a MI with no DACs is -- perfectly valid; use an outer join so that we tolerate the no-DACs case. LEFT JOIN [snapshots].[sysutility_ucp_dac_collected_execution_statistics_internal] ds ON bm.server_instance_name = ds.server_instance_name AND bm.batch_time = ds.batch_time GROUP BY bm.server_instance_name, bm.batch_time, bm.dac_packages_row_count, ds.batch_time HAVING SUM(CASE WHEN ds.batch_time IS NULL THEN 0 ELSE 1 END) = bm.dac_packages_row_count SELECT bm.server_instance_name, bm.batch_time INTO #cpu_memory_configurations_consistent_batches FROM #batch_manifests_latest bm INNER JOIN [snapshots].[sysutility_ucp_cpu_memory_configurations_internal] cm ON bm.server_instance_name = cm.server_instance_name AND bm.batch_time = cm.batch_time GROUP BY bm.server_instance_name, bm.batch_time, bm.cpu_memory_configurations_row_count HAVING COUNT(*) = bm.cpu_memory_configurations_row_count SELECT bm.server_instance_name, bm.batch_time INTO #volumes_consistent_batches FROM #batch_manifests_latest bm INNER JOIN [snapshots].[sysutility_ucp_volumes_internal] vo ON bm.server_instance_name = vo.server_instance_name AND bm.batch_time = vo.batch_time GROUP BY bm.server_instance_name, bm.batch_time, bm.volumes_row_count HAVING COUNT(*) = bm.volumes_row_count SELECT bm.server_instance_name, bm.batch_time INTO #smo_properties_consistent_batches FROM #batch_manifests_latest bm INNER JOIN [snapshots].[sysutility_ucp_smo_properties_internal] sp ON bm.server_instance_name = sp.server_instance_name AND bm.batch_time = sp.batch_time GROUP BY bm.server_instance_name, bm.batch_time, bm.smo_properties_row_count HAVING COUNT(*) = bm.smo_properties_row_count -- Insert the new consistent batch information. A consistent batch is a batch where all of -- the live tables have the expected number of rows. INSERT INTO [sysutility_ucp_staging].[consistent_batch_manifests_internal] SELECT bm.server_instance_name , bm.batch_time FROM ( -- Fetch the latest (order by DESC) consistent batches uploaded by the MI's SELECT ROW_NUMBER() OVER (PARTITION BY bm.server_instance_name ORDER BY bm.batch_time DESC) AS [rank] , bm.server_instance_name , bm.batch_time FROM #batch_manifests_latest AS bm INNER JOIN #dac_statistics_consistent_batches AS ds ON bm.server_instance_name = ds.server_instance_name AND bm.batch_time = ds.batch_time INNER JOIN #cpu_memory_configurations_consistent_batches AS cm ON bm.server_instance_name = cm.server_instance_name AND bm.batch_time = cm.batch_time INNER JOIN #volumes_consistent_batches AS vo ON bm.server_instance_name = vo.server_instance_name AND bm.batch_time = vo.batch_time INNER JOIN #smo_properties_consistent_batches AS sp ON bm.server_instance_name = sp.server_instance_name AND bm.batch_time = sp.batch_time ) bm WHERE bm.[rank] = 1; END GO EXEC sp_addextendedproperty @name = 'MS_UtilityObjectType', @value = 'STAGING', @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_staging', @level1type = 'PROCEDURE', @level1name = 'sp_get_consistent_batches'; GO -- Note: There is another stored procedure in the sysutility_ucp_staging schema -- (sp_copy_live_table_data_into_cache_tables) that is created later in this script, -- because it depends on the schema of some objects in the sysutility_ucp_core schema. /***********************************************************************/ /* Utility SCHEMA: (sysutility_ucp_core) */ /* Dimension Tables and Views */ /* */ /* We currently handle the following dimensions (and hence dimension */ /* tables). */ /* Computers (Table: computers_internal; view: latest_computers) */ /* Volumes (Table: volumes_internal; view: latest_volumes) */ /* Instances (Table: smo_servers_internal; view: latest_smo_servers) */ /* Databases (Table: databases_internal; view: latest_databases) */ /* FileGroups (Table: filegroups_internal; view: latest_filegroups) */ /* DataFiles (Table: datafiles_internal; view: latest_datafiles) */ /* LogFiles (Table: logfiles_internal; view: latest_logfiles) */ /* Dacs (Table: dacs_internal; view: latest_dacs) */ /* */ /* Every dimension table is clustered on its primary key. Each table */ /* has processing_time as the prefix of its primary. This allows for */ /* efficient queue-like behavior - inserts at the end, purges at the */ /* beginning, and queries typically at the end. */ /* */ /* Each of these dimension tables has a corresponding view that picks */ /* the latest consistent information for that dimension. The latest */ /* consistent information is determined by the value of the */ /* "latest_processing_time" column in sysutility_ucp_processing_state */ /* table in MSDB */ /***********************************************************************/ ------------------------------------------------------------------------------------------------------------ -- Table dacs_internal -- This is technically a dimension table for DACs. -- (For various logisical reasons, this also contains CPU utilization information for DACs.) -- The key of this table is (processing_time, server_instance_name, dac_name). -- Machine_name is a regular column, but does not need to be part of the key (server_instance_name already -- includes the appropriate information) ------------------------------------------------------------------------------------------------------------ IF (OBJECT_ID(N'[sysutility_ucp_core].[dacs_internal]', 'U') IS NULL) BEGIN RAISERROR('Creating [sysutility_ucp_core].[dacs_internal] table', 0, 1) WITH NOWAIT; CREATE TABLE [sysutility_ucp_core].[dacs_internal] ( -- todo (VSTS #345036): This column will be removed [dac_id] INT IDENTITY, [server_instance_name] SYSNAME, -- the server-qualified instance name [dac_name] SYSNAME, [urn] NVARCHAR(4000), [powershell_path] NVARCHAR(4000), [physical_server_name] SYSNAME, [dac_deploy_date] DATETIME, [dac_description] NVARCHAR(4000) NULL, -- todo (VSTS #345040) -- This is technically a "measure" column and should not be part of this dimension table [dac_percent_total_cpu_utilization] REAL, [processing_time] DATETIMEOFFSET(7), [batch_time] DATETIMEOFFSET(7), CONSTRAINT [PK_dacs_internal] PRIMARY KEY CLUSTERED (processing_time, server_instance_name, dac_name) ) EXEC sp_addextendedproperty @name = 'MS_UtilityObjectType', @value = 'DIMENSION', @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_core', @level1type = 'TABLE', @level1name = 'dacs_internal'; END GO ------------------------------------------------------------------------------ -- SQL Server View to read latest data for all DACs from cache table -- NOTE: If you change the output of this view in any way, be sure to also update the -- corresponding "stub" object in instmsdb.sql. ------------------------------------------------------------------------------ IF EXISTS (SELECT name FROM sys.views WHERE object_id = OBJECT_ID(N'[sysutility_ucp_core].[latest_dacs]')) BEGIN RAISERROR('Dropping [sysutility_ucp_core].[latest_dacs] view', 0, 1) WITH NOWAIT; DROP VIEW [sysutility_ucp_core].[latest_dacs] END GO RAISERROR('Creating [sysutility_ucp_core].[latest_dacs] view', 0, 1) WITH NOWAIT; GO CREATE VIEW [sysutility_ucp_core].[latest_dacs] AS SELECT dac_id, server_instance_name, dac_name, physical_server_name, dac_deploy_date, dac_description, urn, powershell_path, processing_time, batch_time, dac_percent_total_cpu_utilization FROM [sysutility_ucp_core].[dacs_internal] S WHERE S.processing_time = (SELECT latest_processing_time FROM [msdb].[dbo].[sysutility_ucp_processing_state_internal]) GO EXEC sp_addextendedproperty @name = 'MS_UtilityObjectType', @value = 'CORE', @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_core', @level1type = 'VIEW', @level1name = 'latest_dacs'; GO ----------------------------------------------------------------------- -- -- Dimension table: computers_internal. -- (For logistical reasons, also includes percent_total_cpu_consumption) -- Key: (processing_time, physical_server_name) -- -- NOTE: We use physical_server_name as part of the key rather than the -- (logical) virtual_server_name. This is to allow for scenarios with clustering -- where we have two or more servers clustered together but with potentially -- different configurations. -- ----------------------------------------------------------------------- IF NOT EXISTS (SELECT name FROM sys.objects WHERE object_id = OBJECT_ID(N'[sysutility_ucp_core].[computers_internal]')) BEGIN RAISERROR('Creating table [sysutility_ucp_core].[computers_internal]', 0, 1) WITH NOWAIT; CREATE TABLE [sysutility_ucp_core].[computers_internal] ( -- todo (VSTS #345036): This column will be removed [id] INT IDENTITY, virtual_server_name SYSNAME, physical_server_name SYSNAME, -- differs from virtual_server_name for clustered servers is_clustered_server INT, num_processors INT, cpu_name NVARCHAR(128), cpu_caption NVARCHAR(128), cpu_family NVARCHAR(128), cpu_architecture NVARCHAR(64), cpu_max_clock_speed DECIMAL(10), cpu_clock_speed DECIMAL(10), l2_cache_size DECIMAL(10), l3_cache_size DECIMAL(10), -- todo (VSTS #345040) -- This is technically a "measure" column and should not be part of this dimension table percent_total_cpu_utilization REAL, urn NVARCHAR(4000), powershell_path NVARCHAR(4000), processing_time DATETIMEOFFSET(7), batch_time DATETIMEOFFSET(7), CONSTRAINT [PK_computers_internal] PRIMARY KEY CLUSTERED (processing_time, physical_server_name) ) EXEC sp_addextendedproperty @name = 'MS_UtilityObjectType', @value = 'DIMENSION', @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_core', @level1type = 'TABLE', @level1name = 'computers_internal'; END GO ----------------------------------------------------------------------- -- View to select latest data from [computers_internal] dimension table. -- NOTE: If you change the shape of this view in any way, be sure to also -- update the corresponding "stub" object in instmsdb.sql. ----------------------------------------------------------------------- IF EXISTS (SELECT name FROM sys.views WHERE object_id = OBJECT_ID(N'[sysutility_ucp_core].[latest_computers]')) BEGIN RAISERROR('Dropping [sysutility_ucp_core].[latest_computers] view', 0, 1) WITH NOWAIT; DROP VIEW [sysutility_ucp_core].[latest_computers] END GO RAISERROR('Creating view [sysutility_ucp_core].[latest_computers]', 0, 1) WITH NOWAIT; GO CREATE VIEW [sysutility_ucp_core].[latest_computers] AS SELECT [id], virtual_server_name, physical_server_name, is_clustered_server, num_processors, cpu_name, cpu_caption, cpu_family, cpu_architecture, cpu_max_clock_speed, cpu_clock_speed, l2_cache_size, l3_cache_size, urn, powershell_path, processing_time, batch_time, percent_total_cpu_utilization FROM [sysutility_ucp_core].[computers_internal] WHERE processing_time = (SELECT latest_processing_time FROM [msdb].[dbo].[sysutility_ucp_processing_state_internal]); GO EXEC sp_addextendedproperty @name = 'MS_UtilityObjectType', @value = 'CORE', @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_core', @level1type = 'VIEW', @level1name = 'latest_computers'; GO ----------------------------------------------------------------------- -- Dimension table: volumes_internal -- - PK (processing_time, physical_server_name, volume_name) -- - also includes physical_server_name -- -- - Includes "measure" columns (total_space_available, free_space) -- Created cache table [sysutility_ucp_core].[volumes_internal] for storage view. ----------------------------------------------------------------------- IF NOT EXISTS (SELECT name FROM sys.objects WHERE object_id = OBJECT_ID(N'[sysutility_ucp_core].[volumes_internal]') ) BEGIN RAISERROR('Creating table [sysutility_ucp_core].[volumes_internal]', 0, 1) WITH NOWAIT; CREATE TABLE [sysutility_ucp_core].[volumes_internal] ( -- todo (VSTS #345036): This column will be removed [ID] INT IDENTITY, virtual_server_name SYSNAME, physical_server_name SYSNAME, volume_device_id SYSNAME, volume_name NVARCHAR(260), -- todo (VSTS #345040) -- These are technically "measure" columns and should not be part of this dimension table total_space_available real, -- in MB free_space real, -- in MB processing_time DATETIMEOFFSET(7), batch_time DATETIMEOFFSET(7), powershell_path NVARCHAR(4000) NULL, CONSTRAINT pk_volumes_internal PRIMARY KEY CLUSTERED(processing_time, physical_server_name, volume_device_id) ) EXEC sp_addextendedproperty @name = 'MS_UtilityObjectType', @value = 'DIMENSION', @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_core', @level1type = 'TABLE', @level1name = 'volumes_internal'; END GO -- If we are upgrading a SQL 2008 R2 MDW database, add the powershell_path column to volumes_internal for -- performance reasons (this column was not in the table in SQL Server 2008 R2). See changelists 1844247 -- and 1797832 for background. IF NOT EXISTS (SELECT * FROM sys.columns WHERE name = 'powershell_path' AND object_id = OBJECT_ID('sysutility_ucp_core.volumes_internal')) BEGIN RAISERROR('Adding powershell_path column to [sysutility_ucp_core].[volumes_internal]', 0, 1) WITH NOWAIT; ALTER TABLE sysutility_ucp_core.volumes_internal ADD powershell_path NVARCHAR(4000) NULL; END; GO ------------------------------------------------------------------------------ -- SQL Server View to read latest information for volumes -- NOTE: If you change the output of this view in any way, be sure to also update the -- corresponding "stub" object in instmsdb.sql. ------------------------------------------------------------------------------ IF EXISTS (SELECT name FROM sys.views WHERE object_id = OBJECT_ID(N'[sysutility_ucp_core].[latest_volumes]')) BEGIN RAISERROR('Dropping view [sysutility_ucp_core].[latest_volumes]', 0, 1) WITH NOWAIT; DROP VIEW [sysutility_ucp_core].[latest_volumes] END GO RAISERROR('Creating view [sysutility_ucp_core].[latest_volumes]', 0, 1) WITH NOWAIT; GO CREATE VIEW [sysutility_ucp_core].[latest_volumes] AS SELECT [ID], [virtual_server_name], [physical_server_name], [volume_device_id], [volume_name], [powershell_path], [processing_time], [batch_time], [total_space_available], (total_space_available - free_space) AS [total_space_utilized], (CASE WHEN total_space_available = 0 THEN 0 ELSE (100 * (total_space_available - free_space))/total_space_available END) AS [percent_total_space_utilization] FROM [sysutility_ucp_core].[volumes_internal] WHERE processing_time = (SELECT latest_processing_time FROM [msdb].[dbo].[sysutility_ucp_processing_state_internal]) GO EXEC sp_addextendedproperty @name = 'MS_UtilityObjectType', @value = 'CORE', @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_core', @level1type = 'VIEW', @level1name = 'latest_volumes'; GO -- ============================================= -- Dimension Table for SQL Server Instances -- Table: smo_servers_internal -- Key: processing_time, server_instance_name -- ============================================= IF(OBJECT_ID(N'[sysutility_ucp_core].[smo_servers_internal]', 'U') IS NULL) BEGIN RAISERROR ('Creating table [sysutility_ucp_core].[smo_servers_internal]', 0, 1) WITH NOWAIT; CREATE TABLE [sysutility_ucp_core].[smo_servers_internal] ( [urn] NVARCHAR(320) , [powershell_path] NVARCHAR(4000) , [processing_time] DATETIMEOFFSET(7) , [batch_time] DATETIMEOFFSET(7) -- SMO properties , [AuditLevel] SMALLINT , [BackupDirectory] NVARCHAR(260) , [BrowserServiceAccount] NVARCHAR(128) , [BrowserStartMode] SMALLINT , [BuildClrVersionString] NVARCHAR(20) , [BuildNumber] INT , [Collation] NVARCHAR(128) , [CollationID] INT , [ComparisonStyle] INT , [ComputerNamePhysicalNetBIOS] NVARCHAR(128) , [DefaultFile] NVARCHAR(260) , [DefaultLog] NVARCHAR(260) , [Edition] NVARCHAR(64) , [EngineEdition] SMALLINT , [ErrorLogPath] NVARCHAR(260) , [FilestreamShareName] NVARCHAR(260) , [InstallDataDirectory] NVARCHAR(260) , [InstallSharedDirectory] NVARCHAR(260) , [InstanceName] NVARCHAR(128) , [IsCaseSensitive] BIT , [IsClustered] BIT , [IsFullTextInstalled] BIT , [IsSingleUser] BIT , [Language] NVARCHAR(64) , [MailProfile] NVARCHAR(128) , [MasterDBLogPath] NVARCHAR(260) , [MasterDBPath] NVARCHAR(260) , [MaxPrecision] TINYINT , [Name] NVARCHAR(128) -- This is SERVERPROPERTY('ServerName') , [NamedPipesEnabled] BIT , [NetName] NVARCHAR(128) -- This is SERVERPROPERTY('MachineName') , [NumberOfLogFiles] INT , [OSVersion] NVARCHAR(32) , [PerfMonMode] SMALLINT , [PhysicalMemory] INT , [Platform] NVARCHAR(32) , [Processors] SMALLINT , [ProcessorUsage] INT , [Product] NVARCHAR(48) , [ProductLevel] NVARCHAR(32) , [ResourceVersionString] NVARCHAR(32) , [RootDirectory] NVARCHAR(260) , [ServerType] SMALLINT , [ServiceAccount] NVARCHAR(128) , [ServiceInstanceId] NVARCHAR(64) , [ServiceName] NVARCHAR(64) , [ServiceStartMode] SMALLINT , [SqlCharSet] SMALLINT , [SqlCharSetName] NVARCHAR(32) , [SqlDomainGroup] NVARCHAR(260) , [SqlSortOrder] SMALLINT , [SqlSortOrderName] NVARCHAR(64) , [Status] SMALLINT , [TapeLoadWaitTime] INT , [TcpEnabled] BIT , [VersionMajor] INT , [VersionMinor] INT , [VersionString] NVARCHAR(32) CONSTRAINT [PK_smo_servers_internal] PRIMARY KEY CLUSTERED (processing_time, [Name]) -- NOTE: compression is enabled on this table at runtime (during Create UCP) in sp_initialize_mdw_internal ); EXEC sp_addextendedproperty @name = 'MS_UtilityObjectType', @value = 'DIMENSION', @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_core', @level1type = 'TABLE', @level1name = 'smo_servers_internal'; END GO -- ===================================================================== -- Dimension Table for databases in a SQL Instance -- Table: databases_internal -- Key: processing_time, server_instance_name, name -- ===================================================================== IF(OBJECT_ID(N'[sysutility_ucp_core].[databases_internal]', 'U') IS NULL) BEGIN RAISERROR ('Creating table [sysutility_ucp_core].[databases_internal]', 0, 1) WITH NOWAIT; CREATE TABLE [sysutility_ucp_core].[databases_internal] ( [urn] NVARCHAR(512) , [powershell_path] NVARCHAR(MAX) , [processing_time] DATETIMEOFFSET(7) , [batch_time] DATETIMEOFFSET(7) , [server_instance_name] SYSNAME , [parent_urn] NVARCHAR(320) , [Collation] NVARCHAR(128) , [CompatibilityLevel] SMALLINT , [CreateDate] DATETIME , [EncryptionEnabled] BIT , [Name] SYSNAME , [RecoveryModel] SMALLINT , [Trustworthy] BIT , [state] TINYINT NULL CONSTRAINT [PK_databases_internal] PRIMARY KEY CLUSTERED (processing_time, server_instance_name, [Name]) -- Note: compression is enabled on this table at runtime (during Create UCP) in sp_initialize_mdw_internal ); EXEC sp_addextendedproperty @name = 'MS_UtilityObjectType', @value = 'DIMENSION', @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_core', @level1type = 'TABLE', @level1name = 'databases_internal'; END GO -- If we are upgrading a SQL 2008 R2 MDW database, add the state column to databases_internal. This column allows -- better handling of unavailable databases (offline, emergency mode, recovering, etc). See changelist 1856917 -- and VSTS 624995 for background. IF NOT EXISTS ( SELECT * FROM sys.columns WHERE name = 'state' AND object_id = OBJECT_ID('sysutility_ucp_core.databases_internal')) BEGIN RAISERROR('Adding state column to [sysutility_ucp_core].[databases_internal]', 0, 1) WITH NOWAIT; ALTER TABLE sysutility_ucp_core.databases_internal ADD state TINYINT NULL; END; GO -- Constrain the values allowed in the state column. IF NOT EXISTS ( SELECT * FROM sys.check_constraints WHERE name = 'chk_databases_internal_state' AND parent_object_id = OBJECT_ID('sysutility_ucp_core.databases_internal')) BEGIN -- Current defined states are 0 (available) and 1 (not available -- emergency mode, offline, etc) ALTER TABLE sysutility_ucp_core.databases_internal ADD CONSTRAINT chk_databases_internal_state CHECK ([state] BETWEEN 0 AND 1); END; GO -- ===================================================================== -- Dimension Table for filegroups in a database (in a SQL Instance) -- Table: filegroups_internal -- Key: processing_time, server_instance_name, database_name, name -- ===================================================================== IF(OBJECT_ID(N'[sysutility_ucp_core].[filegroups_internal]', 'U') IS NULL) BEGIN RAISERROR ('Creating table [sysutility_ucp_core].[filegroups_internal]', 0, 1) WITH NOWAIT; CREATE TABLE [sysutility_ucp_core].[filegroups_internal] ( [urn] NVARCHAR(780) , [powershell_path] NVARCHAR(MAX) , [processing_time] DATETIMEOFFSET(7) , [batch_time] DATETIMEOFFSET(7) , [server_instance_name] SYSNAME , [database_name] SYSNAME , [parent_urn] NVARCHAR(512) -- SMO Properties , [Name] SYSNAME , CONSTRAINT PK_filegroups_internal PRIMARY KEY CLUSTERED(processing_time, server_instance_name, database_name, [Name]) -- Note: compression is enabled on this table at runtime (during Create UCP) in sp_initialize_mdw_internal ); EXEC sp_addextendedproperty @name = 'MS_UtilityObjectType', @value = 'DIMENSION', @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_core', @level1type = 'TABLE', @level1name = 'filegroups_internal'; END GO -- ===================================================================== -- Dimension Table for datafiles in a database (in a SQL Instance) -- Table: datafiles_internal -- Key: processing_time, server_instance_name, database_name, filegroup_name, name -- -- VSTS #345570: The key length of the clustered index may be larger than 900 bytes. -- -- ===================================================================== IF(OBJECT_ID(N'[sysutility_ucp_core].[datafiles_internal]', 'U') IS NULL) BEGIN RAISERROR ('Creating table [sysutility_ucp_core].[datafiles_internal]', 0, 1) WITH NOWAIT; CREATE TABLE [sysutility_ucp_core].[datafiles_internal] ( [urn] NVARCHAR(1500) , [powershell_path] NVARCHAR(MAX) , [processing_time] DATETIMEOFFSET(7) , [batch_time] DATETIMEOFFSET(7) , [server_instance_name] SYSNAME , [database_name] SYSNAME , [filegroup_name] SYSNAME , [parent_urn] NVARCHAR(780) , [physical_server_name] SYSNAME , [volume_name] NVARCHAR(260) , [volume_device_id] SYSNAME -- SMO Properties , [Growth] REAL , [GrowthType] SMALLINT , [MaxSize] REAL , [Name] SYSNAME , [Size] REAL , [UsedSpace] REAL , [FileName] NVARCHAR(260) , [VolumeFreeSpace] BIGINT , CONSTRAINT PK_datafiles_internal PRIMARY KEY CLUSTERED (processing_time, server_instance_name, database_name, [filegroup_name], [Name]) -- Note: compression is enabled on this table at runtime (during Create UCP) in sp_initialize_mdw_internal ); EXEC sp_addextendedproperty @name = 'MS_UtilityObjectType', @value = 'DIMENSION', @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_core', @level1type = 'TABLE', @level1name = 'datafiles_internal'; END GO -- ===================================================================== -- Dimension Table for logfiles in a database (in a SQL Instance) -- Table: logfiles_internal -- Key: processing_time, server_instance_name, database_name, name -- ===================================================================== IF(OBJECT_ID(N'[sysutility_ucp_core].[logfiles_internal]', 'U') IS NULL) BEGIN RAISERROR ('Creating table [sysutility_ucp_core].[logfiles_internal]', 0, 1) WITH NOWAIT; CREATE TABLE [sysutility_ucp_core].[logfiles_internal] ( [urn] NVARCHAR(1500) , [powershell_path] NVARCHAR(MAX) , [processing_time] DATETIMEOFFSET(7) , [batch_time] DATETIMEOFFSET(7) , [server_instance_name] SYSNAME , [database_name] SYSNAME , [parent_urn] NVARCHAR(780) , [physical_server_name] SYSNAME , [volume_name] NVARCHAR(260) , [volume_device_id] SYSNAME -- SMO Properties , [Growth] REAL , [GrowthType] SMALLINT , [MaxSize] REAL , [Name] SYSNAME , [Size] REAL , [UsedSpace] REAL , [FileName] NVARCHAR(260) , [VolumeFreeSpace] BIGINT , CONSTRAINT PK_logfiles_internal PRIMARY KEY CLUSTERED (processing_time, server_instance_name, database_name, [Name]) -- Note: compression is enabled on this table at runtime (during Create UCP) in sp_initialize_mdw_internal ); EXEC sp_addextendedproperty @name = 'MS_UtilityObjectType', @value = 'DIMENSION', @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_core', @level1type = 'TABLE', @level1name = 'logfiles_internal'; END GO ----------------------------------------------------------------------------- -- The view which returns server properties -- NOTE: If you change the output of this view in any way, be sure to also update the -- corresponding "stub" object in instmsdb.sql. ----------------------------------------------------------------------------- IF EXISTS (SELECT name FROM sys.views WHERE object_id = OBJECT_ID(N'[sysutility_ucp_core].[latest_smo_servers]')) BEGIN RAISERROR('Dropping [sysutility_ucp_core].[latest_smo_servers] view', 0, 1) WITH NOWAIT; DROP VIEW [sysutility_ucp_core].[latest_smo_servers] END GO RAISERROR('Creating [sysutility_ucp_core].[latest_smo_servers] view', 0, 1) WITH NOWAIT; GO CREATE VIEW [sysutility_ucp_core].[latest_smo_servers] AS SELECT urn , [powershell_path] , [processing_time] , [batch_time] , [AuditLevel] , [BackupDirectory] , [BrowserServiceAccount] , [BrowserStartMode] , [BuildClrVersionString] , [BuildNumber] , [Collation] , [CollationID] , [ComparisonStyle] , [ComputerNamePhysicalNetBIOS] , [DefaultFile] , [DefaultLog] , [Edition] , [EngineEdition] , [ErrorLogPath] , [FilestreamShareName] , [InstallDataDirectory] , [InstallSharedDirectory] , [InstanceName] , [IsCaseSensitive] , [IsClustered] , [IsFullTextInstalled] , [IsSingleUser] , [Language] , [MailProfile] , [MasterDBLogPath] , [MasterDBPath] , [MaxPrecision] , [Name] , [NamedPipesEnabled] , [NetName] , [NumberOfLogFiles] , [OSVersion] , [PerfMonMode] , [PhysicalMemory] , [Platform] , [Processors] , [ProcessorUsage] , [Product] , [ProductLevel] , [ResourceVersionString] , [RootDirectory] , [ServerType] , [ServiceAccount] , [ServiceInstanceId] , [ServiceName] , [ServiceStartMode] , [SqlCharSet] , [SqlCharSetName] , [SqlDomainGroup] , [SqlSortOrder] , [SqlSortOrderName] , [Status] , [TapeLoadWaitTime] , [TcpEnabled] , [VersionMajor] , [VersionMinor] , [VersionString] FROM [sysutility_ucp_core].[smo_servers_internal] AS SI WHERE SI.processing_time = (SELECT latest_processing_time FROM [msdb].[dbo].[sysutility_ucp_processing_state_internal]) GO EXEC sp_addextendedproperty @name = 'MS_UtilityObjectType', @value = 'CORE', @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_core', @level1type = 'VIEW', @level1name = 'latest_smo_servers'; GO ------------------------------------------------------------------------------ -- SQL Server View to read latest snapshot data for SMO Database object -- NOTE: If you change the output of this view in any way, be sure to also update the -- corresponding "stub" object in instmsdb.sql. ------------------------------------------------------------------------------ IF EXISTS (SELECT name FROM sys.views WHERE object_id = OBJECT_ID(N'[sysutility_ucp_core].[latest_databases]')) BEGIN RAISERROR('Dropping [sysutility_ucp_core].[latest_databases] view', 0, 1) WITH NOWAIT; DROP VIEW [sysutility_ucp_core].[latest_databases] END GO RAISERROR('Creating [sysutility_ucp_core].[latest_databases] view', 0, 1) WITH NOWAIT; GO CREATE VIEW [sysutility_ucp_core].[latest_databases] AS SELECT [urn] , [powershell_path] , [processing_time] , [batch_time] , [server_instance_name] , [parent_urn] , [Collation] , [CompatibilityLevel] , [CreateDate] , [EncryptionEnabled] , [Name] , [RecoveryModel] , [Trustworthy] , [state] FROM [sysutility_ucp_core].[databases_internal] AS SI WHERE SI.processing_time = (SELECT latest_processing_time FROM [msdb].[dbo].[sysutility_ucp_processing_state_internal]) GO EXEC sp_addextendedproperty @name = 'MS_UtilityObjectType', @value = 'CORE', @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_core', @level1type = 'VIEW', @level1name = 'latest_databases'; GO ------------------------------------------------------------------------------ -- SQL Server View to read latest snapshot data for SMO FileGroup object -- NOTE: If you change the output of this view in any way, be sure to also update the -- corresponding "stub" object in instmsdb.sql. ------------------------------------------------------------------------------ IF EXISTS (SELECT name FROM sys.views WHERE object_id = OBJECT_ID(N'[sysutility_ucp_core].[latest_filegroups]')) BEGIN RAISERROR('Dropping [sysutility_ucp_core].[latest_filegroups] view', 0, 1) WITH NOWAIT; DROP VIEW [sysutility_ucp_core].[latest_filegroups] END GO RAISERROR('Creating [sysutility_ucp_core].[latest_filegroups] view', 0, 1) WITH NOWAIT; GO CREATE VIEW [sysutility_ucp_core].[latest_filegroups] AS SELECT [urn] , [powershell_path] , [processing_time] , [batch_time] , [server_instance_name] , [database_name] , [parent_urn] , [Name] FROM [sysutility_ucp_core].[filegroups_internal] AS SI WHERE SI.processing_time = (SELECT latest_processing_time FROM [msdb].[dbo].[sysutility_ucp_processing_state_internal]) -- Suppress for "not available" databases (state=1). We lack full filegroup/file hierarchy metadata for these databases. AND EXISTS ( SELECT * FROM sysutility_ucp_core.latest_databases AS db WHERE db.server_instance_name = SI.server_instance_name AND db.Name = SI.database_name AND db.state = 0) GO EXEC sp_addextendedproperty @name = 'MS_UtilityObjectType', @value = 'CORE', @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_core', @level1type = 'VIEW', @level1name = 'latest_filegroups'; GO ------------------------------------------------------------------------------ -- SQL Server View to read latest snapshot data for SMO DataFile object -- NOTE: If you change the output of this view in any way, be sure to also update the -- corresponding "stub" object in instmsdb.sql. ------------------------------------------------------------------------------ IF EXISTS (SELECT name FROM sys.views WHERE object_id = OBJECT_ID(N'[sysutility_ucp_core].[latest_datafiles]')) BEGIN RAISERROR('Dropping [sysutility_ucp_core].[latest_datafiles] view', 0, 1) WITH NOWAIT; DROP VIEW [sysutility_ucp_core].[latest_datafiles] END GO RAISERROR('Creating [sysutility_ucp_core].[latest_datafiles] view', 0, 1) WITH NOWAIT; GO CREATE VIEW [sysutility_ucp_core].[latest_datafiles] AS SELECT [urn] , [powershell_path] , [processing_time] , [batch_time] , [server_instance_name] , [database_name] , [filegroup_name] , [parent_urn] , [physical_server_name] , [volume_name] , [volume_device_id] , [Growth] , [GrowthType] , [MaxSize] , [Name] , [Size] , [UsedSpace] , [FileName] , [VolumeFreeSpace] , [sysutility_ucp_misc].[fn_get_max_size_available]([Size], [MaxSize], [Growth], [GrowthType], [VolumeFreeSpace]) AS [available_space] FROM [sysutility_ucp_core].[datafiles_internal] AS SI WHERE SI.processing_time = (SELECT latest_processing_time FROM [msdb].[dbo].[sysutility_ucp_processing_state_internal]) -- Suppress for "not available" databases (state=1). We lack full filegroup/file hierarchy metadata for these databases. AND EXISTS ( SELECT * FROM sysutility_ucp_core.latest_databases AS db WHERE db.server_instance_name = SI.server_instance_name AND db.Name = SI.database_name AND db.state = 0) GO EXEC sp_addextendedproperty @name = 'MS_UtilityObjectType', @value = 'CORE', @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_core', @level1type = 'VIEW', @level1name = 'latest_datafiles'; GO ------------------------------------------------------------------------------ -- SQL Server View to read latest snapshot data for SMO DataFile object -- NOTE: If you change the output of this view in any way, be sure to also update the -- corresponding "stub" object in instmsdb.sql. ------------------------------------------------------------------------------ IF EXISTS (SELECT name FROM sys.views WHERE object_id = OBJECT_ID(N'[sysutility_ucp_core].[latest_logfiles]')) BEGIN RAISERROR('Dropping [sysutility_ucp_core].[latest_logfiles] view', 0, 1) WITH NOWAIT; DROP VIEW [sysutility_ucp_core].[latest_logfiles] END GO RAISERROR('Creating [sysutility_ucp_core].[latest_logfiles] view', 0, 1) WITH NOWAIT; GO CREATE VIEW [sysutility_ucp_core].[latest_logfiles] AS SELECT [urn] , [powershell_path] , [processing_time] , [batch_time] , [server_instance_name] , [database_name] , [parent_urn] , [physical_server_name] , [volume_name] , [volume_device_id] , [Growth] , [GrowthType] , [MaxSize] , [Name] , [Size] , [UsedSpace] , [FileName] , [VolumeFreeSpace] , [sysutility_ucp_misc].[fn_get_max_size_available]([Size], [MaxSize], [Growth], [GrowthType], [VolumeFreeSpace]) AS [available_space] FROM [sysutility_ucp_core].[logfiles_internal] AS SI WHERE SI.processing_time = (SELECT latest_processing_time FROM [msdb].[dbo].[sysutility_ucp_processing_state_internal]) -- Suppress for "not available" databases (state=1). We lack full filegroup/file hierarchy metadata for these databases. AND EXISTS ( SELECT * FROM sysutility_ucp_core.latest_databases AS db WHERE db.server_instance_name = SI.server_instance_name AND db.Name = SI.database_name AND db.state = 0) GO EXEC sp_addextendedproperty @name = 'MS_UtilityObjectType', @value = 'CORE', @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_core', @level1type = 'VIEW', @level1name = 'latest_logfiles'; GO /***********************************************************************/ /* Utility SCHEMA: (sysutility_ucp_core) */ /* Measure Tables and Views */ /* */ /* We currently handle the following measures (and hence measure */ /* tables). */ /* CPU (Table: cpu_utilization_internal) */ /* Storage Space (Table: space_utilization_internal) */ /* */ /* CPU information is stored for the following dimensions */ /* - Computers */ /* - Instances */ /* - Dacs */ /* */ /* Storage space information is stored for the following dimensions */ /* - Computers */ /* - Volumes */ /* - Instances */ /* - Databases */ /* - FileGroups */ /* - DataFiles */ /* - LogFiles */ /* */ /* Each measure table stores information at different levels of */ /* aggregation. Currently, we support 3 levels of aggregation */ /* - No aggregation (i.e.) latest "detail" information */ /* - Hourly */ /* - Daily */ /* We expect to add additional aggregation levels in the future */ /* */ /* Information for each aggregation level is stored in a different */ /* partition of the measure table. This allows us to leverage */ /* partition pruning (for queries), and different maintenance operations */ /* for each partition (especially wrt purging of data. */ /* */ /* Within each partition, (processing_time, object_type) */ /* is the prefix of the key. This allows for inserts at the end, and */ /* purges at the front for new data. It also ensures that information */ /* about each object type is collocated within a given collection time */ /* */ /***********************************************************************/ --- -- Describes the aggregation level -- 0 = Non-aggregated; 1 = Hourly, 2 = Daily, ... -- IF NOT EXISTS(SELECT 0 FROM sys.types t WHERE t.name = N'AggregationType' AND t.schema_id = SCHEMA_ID(N'sysutility_ucp_core')) BEGIN RAISERROR ('Creating type [sysutility_ucp_core].[AggregationType]', 0, 1) WITH NOWAIT; CREATE TYPE [sysutility_ucp_core].[AggregationType] FROM TINYINT END GO -- 0 = utility -- 1 = computer -- 2 = volume -- 3 = instance -- 4 = database (also dac) -- 5 = filegroup -- 6 = datafile -- 7 = logfile IF NOT EXISTS(SELECT 0 FROM sys.types t WHERE t.name = N'ObjectType' AND t.[schema_id] = SCHEMA_ID(N'sysutility_ucp_core')) BEGIN RAISERROR ('Creating type [sysutility_ucp_core].[ObjectType]', 0, 1) WITH NOWAIT; CREATE TYPE [sysutility_ucp_core].[ObjectType] FROM TINYINT END GO -- ============================================================================ -- Measure Table: CPU Utilization information -- Supported dimensions: Computers, Instances, Databases (DACs) -- Supported aggregation-levels: none, hourly, daily -- -- The object_type field describes the dimension that the current entry (row) -- defines. -- The following conditions must hold. -- If object_type = 1, server_instance_name = NULL and database_name = NULL and physical_server_name <> NULL -- If object type = 3, physical_server_name = NULL, database_name = NULL, server_instance_name <> NULL -- If object_type = 4, physical_server_name = NULL, server_instance_name <> NULL, database_name <> NULL -- No other legal combinations -- ============================================================================ IF(OBJECT_ID(N'[sysutility_ucp_core].[cpu_utilization_internal]', N'U') IS NULL) BEGIN RAISERROR ('Creating table [sysutility_ucp_core].[cpu_utilization_internal]', 0, 1) WITH NOWAIT; CREATE TABLE [sysutility_ucp_core].[cpu_utilization_internal] ( [processing_time] DATETIMEOFFSET(7) NOT NULL, [aggregation_type] [sysutility_ucp_core].AggregationType NOT NULL, [object_type] [sysutility_ucp_core].ObjectType NOT NULL, -- Dimension keys [physical_server_name] SYSNAME DEFAULT N'', [server_instance_name] SYSNAME DEFAULT N'', [database_name] SYSNAME DEFAULT N'', -- The actual measure columns. percent_total_cpu_utilization REAL, -- NOTE: This index is redefined at runtime (during Create UCP) in sp_initialize_mdw_internal CONSTRAINT pk_cpu_utilization_internal PRIMARY KEY CLUSTERED(aggregation_type, processing_time, object_type, physical_server_name, server_instance_name, database_name) ) EXEC sp_addextendedproperty @name = 'MS_UtilityObjectType', @value = 'MEASURE', @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_core', @level1type = 'TABLE', @level1name = 'cpu_utilization_internal'; END GO --------------------------------------------------------------------------- -- View to select information from the [cpu_utilization_internal] measure -- table. -- NOTE: If you change the shape of this view in any way, be sure to also -- update the corresponding "stub" object in instmsdb.sql. ----------------------------------------------------------------------- IF EXISTS (SELECT name FROM sys.views WHERE object_id = OBJECT_ID(N'[sysutility_ucp_core].[cpu_utilization]')) BEGIN RAISERROR('Dropping [sysutility_ucp_core].[cpu_utilization] view', 0, 1) WITH NOWAIT; DROP VIEW [sysutility_ucp_core].[cpu_utilization] END GO RAISERROR('Creating [sysutility_ucp_core].[cpu_utilization] view', 0, 1) WITH NOWAIT; GO CREATE VIEW [sysutility_ucp_core].[cpu_utilization] AS SELECT aggregation_type, processing_time, object_type, physical_server_name, server_instance_name, database_name, percent_total_cpu_utilization FROM [sysutility_ucp_core].[cpu_utilization_internal] GO EXEC sp_addextendedproperty @name = 'MS_UtilityObjectType', @value = 'CORE', @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_core', @level1type = 'VIEW', @level1name = 'cpu_utilization'; GO -- ============================================================================ -- Measure Table: Space Utilization information -- Supported dimensions: Computers, Volumes, Instances, Databases, -- Filegroups, DataFiles, LogFiles -- Supported aggregation-levels: none, hourly, daily -- -- The object_type field describes the dimension that the current entry (row) -- defines. -- The following conditions must hold. -- Utility (type=0), all dimension columns must be '' -- Computer (type=1), virtual_server_name must be non-null; the rest must be '' -- Volume (type=2), virtual_server_name, volume_device_id must be non-null; rest '' -- Instance (type=3), server_instance_name must be non-NULL, everything else must be '' -- Database (type=4), server_instance_name, database_name must be non-null -- other keys must be '' -- FileGroup(type=5), server_instance_name, datbase_name, filegroup_name must be non-NULL -- other keys must be '' -- DataFile (type=6), server_instance_name, database_name, filegroup_name, dbfile_name -- must be non-null. Other keys must be '' -- LogFile (type=7), server_instance_name, database_name, dbfile_name must be non-null -- other keys must be '' -- -- IMPORTANT: Unlike the cpu_utilization measure table, the space_utilization measure -- table uses virtual_server_name to represent a computer (and volume). This is -- because storage is shared (potentially) across a failover-cluster-instance, -- and we want to track history of the space usage regardless of the specific -- current "owner" of the storage -- -- The space_utilization_internal table stores information about two distinct hierarchies. -- The Utility->Computer->Volume hierarchy and the Instance->Database->FileGroup->File -- hiererachy. A row in the table is part of only one of these hierarchies. -- -- There are 4 distinct measure columns we maintain in this table. Not all of them are -- really need. See VSTS #345039 -- total_space_bytes : the maximum amount of storage available -- allocated_space_bytes: the currently allocated amount of storage. Typically, the -- same as total_space_bytes, except for files -- used_space_bytes : the current used up space -- available_space_bytes: the amount of space that's available for further use. -- Typically this is total_space_bytes - used_space_bytes. -- Except for files, where this is more complicated -- -- We should be able to live with just two of these columns (total_space and used_space) -- -- For the instance-database-... hierarchy, only used_space_bytes is rolled up. All -- the other values are rolled up to NULL -- For the Utility-computer-volume hierarchy, all values are rolled up. -- -- -- -- VSTS #345570: The key length of the clustered index may be larger than 900 bytes. -- -- ============================================================================ IF(OBJECT_ID(N'[sysutility_ucp_core].[space_utilization_internal]', N'U') IS NULL) BEGIN RAISERROR ('Creating table [sysutility_ucp_core].[space_utilization_internal]', 0, 1) WITH NOWAIT; CREATE TABLE [sysutility_ucp_core].[space_utilization_internal] ( [processing_time] DATETIMEOFFSET(7) NOT NULL, [aggregation_type] [sysutility_ucp_core].AggregationType NOT NULL, [object_type] [sysutility_ucp_core].ObjectType NOT NULL, -- The dimension columns [virtual_server_name] SYSNAME DEFAULT N'', [server_instance_name] SYSNAME DEFAULT N'', [volume_device_id] SYSNAME DEFAULT N'', [database_name] SYSNAME DEFAULT N'', [filegroup_name] SYSNAME DEFAULT N'', [dbfile_name] SYSNAME DEFAULT N'', -- todo (VSTS #345039) -- we don't need all 4 of the columns below. We only need used_space and available_space [used_space_bytes] REAL, [allocated_space_bytes] REAL, [total_space_bytes] REAL, [available_space_bytes] REAL, -- NOTE: This index is redefined at runtime (during Create UCP) in sp_initialize_mdw_internal CONSTRAINT pk_storage_utilization PRIMARY KEY CLUSTERED( aggregation_type, processing_time, object_type, virtual_server_name, volume_device_id, server_instance_name, database_name, [filegroup_name], dbfile_name) ) EXEC sp_addextendedproperty @name = 'MS_UtilityObjectType', @value = 'MEASURE', @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_core', @level1type = 'TABLE', @level1name = 'space_utilization_internal'; END GO --------------------------------------------------------------------------- -- View to select information from the [space_utilization_internal] measure -- table. -- NOTE: If you change the shape of this view in any way, be sure to also -- update the corresponding "stub" object in instmsdb.sql. ----------------------------------------------------------------------- IF EXISTS (SELECT name FROM sys.views WHERE object_id = OBJECT_ID(N'[sysutility_ucp_core].[space_utilization]')) BEGIN RAISERROR('Dropping [sysutility_ucp_core].[space_utilization] view', 0, 1) WITH NOWAIT; DROP VIEW [sysutility_ucp_core].[space_utilization] END GO RAISERROR('Creating [sysutility_ucp_core].[space_utilization] view', 0, 1) WITH NOWAIT; GO CREATE VIEW [sysutility_ucp_core].[space_utilization] AS SELECT aggregation_type, processing_time, object_type, virtual_server_name, volume_device_id, server_instance_name, database_name, [filegroup_name], dbfile_name, total_space_bytes, allocated_space_bytes, used_space_bytes, available_space_bytes FROM [sysutility_ucp_core].[space_utilization_internal] GO EXEC sp_addextendedproperty @name = 'MS_UtilityObjectType', @value = 'CORE', @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_core', @level1type = 'VIEW', @level1name = 'space_utilization'; GO ----------------------------------------------------------------------------------------- -- Procedure sp_copy_live_table_data_into_cache_tables -- Copies the latest snapshot of data from the "live" tables into the "cache" tables ----------------------------------------------------------------------------------------- IF OBJECT_ID ('sysutility_ucp_staging.sp_copy_live_table_data_into_cache_tables') IS NOT NULL BEGIN RAISERROR('Dropping procedure sysutility_ucp_staging.sp_copy_live_table_data_into_cache_tables procedure', 0, 1) WITH NOWAIT; DROP PROCEDURE sysutility_ucp_staging.sp_copy_live_table_data_into_cache_tables; END GO RAISERROR('Creating procedure sysutility_ucp_staging.sp_copy_live_table_data_into_cache_tables', 0, 1) WITH NOWAIT; GO CREATE PROCEDURE sysutility_ucp_staging.sp_copy_live_table_data_into_cache_tables AS BEGIN SET NOCOUNT ON; -- Snapshot isolation prevents the nightly purge jobs that delete much older data from blocking us. SET TRANSACTION ISOLATION LEVEL SNAPSHOT; DECLARE @max_snapshot_id INT, @num_snapshots_partitions INT SELECT @max_snapshot_id = ISNULL(MAX(snapshot_id),0) FROM [core].[snapshots] SELECT @num_snapshots_partitions = COUNT(*) FROM [msdb].[dbo].[sysutility_ucp_snapshot_partitions_internal] DECLARE @task_start_time DATETIME = GETUTCDATE(); DECLARE @task_elapsed_ms INT; DECLARE @row_count INT; -- Initialize the snapshot partitions to default (0) IF(@num_snapshots_partitions = 0) BEGIN INSERT INTO [msdb].[dbo].[sysutility_ucp_snapshot_partitions_internal] VALUES (2, 0) INSERT INTO [msdb].[dbo].[sysutility_ucp_snapshot_partitions_internal] VALUES (1, 0) INSERT INTO [msdb].[dbo].[sysutility_ucp_snapshot_partitions_internal] VALUES (0, 0) END DECLARE @processing_time_current DATETIMEOFFSET(7) = SYSDATETIMEOFFSET(); -- -- Stage 0: -- Identify the batches that were recently uploaded and are consistent -- Data belonging to these batches will be copied from live to cache table. EXEC [sysutility_ucp_staging].[sp_get_consistent_batches] SET @task_elapsed_ms = DATEDIFF (ms, @task_start_time, GETUTCDATE()); RAISERROR ('sp_get_consistent_batches: %d ms', 0, 1, @task_elapsed_ms); SET @task_start_time = GETUTCDATE(); ----- ----- Stage 1: Insert into all the dimension tables ----- computers_internal, dacs_internal, volumes_internal, ----- smo_servers_internal, databases_internal, filegroups_internal, ----- datafiles_internal, logfiles_internal ----- (Then move to Stage 2: the "measure" tables) ----- -- A note about the expression used for [batch_time] in the INSERT queries, below: -- -- We want to expose batch_time w/a UCP-local time zone so it can be exposed as a UCP-local datetime -- in the GUI (the GUI consumes directly from the enumerator). The batch_time values in each of the -- queries below have their time zone offset switched to produce a datetimeoffset with the local UCP -- server's time zone offset. -- -- This works well except when the server's time zone offset has changed since the [batch_time] was -- generated due to a Daylight Saving Time change. (Unfortunately, there is no way in T-SQL to -- determine what the server's time zone offset was at some arbitrary point in the past.) The risk -- of this is low since we generally do this processing within 15 minutes of timestamp generation. -- This doesn't actually result in truly incorrect batch_times that would affect data processing -- since the UTC time value that underlies every datetimeoffset is unchanged when you switch the -- value's time zone offset. -- -- Insert into the "computers" dimension table -- INSERT INTO [sysutility_ucp_core].[computers_internal] ( virtual_server_name, is_clustered_server, physical_server_name, num_processors, cpu_name, cpu_caption, cpu_family, cpu_architecture, cpu_max_clock_speed, cpu_clock_speed, l2_cache_size, l3_cache_size, percent_total_cpu_utilization, batch_time, processing_time, urn, powershell_path) SELECT virtual_server_name, is_clustered_server, physical_server_name, num_processors, cpu_name, cpu_caption, cpu_family, cpu_architecture, cpu_max_clock_speed, cpu_clock_speed, l2_cache_size, l3_cache_size, server_processor_usage, SWITCHOFFSET (batch_time, DATENAME(TZoffset, SYSDATETIMEOFFSET())) AS batch_time, @processing_time_current AS processing_time, urn, powershell_path FROM [sysutility_ucp_staging].[latest_computer_cpu_memory_configuration] SET @row_count = @@ROWCOUNT; SET @task_elapsed_ms = DATEDIFF (ms, @task_start_time, GETUTCDATE()); RAISERROR ('Insert into [computers_internal]: %d rows, %d ms', 0, 1, @row_count, @task_elapsed_ms); SET @task_start_time = GETUTCDATE(); -- -- Insert into the "dacs_internal" dimension-table -- INSERT INTO [sysutility_ucp_core].[dacs_internal] ( server_instance_name, dac_name, physical_server_name, dac_deploy_date, dac_description, dac_percent_total_cpu_utilization, batch_time, processing_time, urn, powershell_path) SELECT server_instance_name, dac_name, physical_server_name, dac_deploy_date, dac_description, latest_cpu_pct, SWITCHOFFSET (batch_time, DATENAME(TZoffset, SYSDATETIMEOFFSET())) AS batch_time, @processing_time_current AS processing_time, urn, powershell_path FROM [sysutility_ucp_staging].[latest_dac_cpu_utilization] SET @row_count = @@ROWCOUNT; SET @task_elapsed_ms = DATEDIFF (ms, @task_start_time, GETUTCDATE()); RAISERROR ('Insert into [dacs_internal]: %d rows, %d ms', 0, 1, @row_count, @task_elapsed_ms); SET @task_start_time = GETUTCDATE(); --- Insert into the volumes_internal dimension table INSERT INTO [sysutility_ucp_core].[volumes_internal] ( virtual_server_name, physical_server_name, volume_device_id, volume_name, total_space_available, free_space, powershell_path, batch_time, processing_time) SELECT virtual_server_name, physical_server_name, volume_device_id, volume_name, total_space_available, free_space, powershell_path, SWITCHOFFSET (batch_time, DATENAME(TZoffset, SYSDATETIMEOFFSET())) AS batch_time, @processing_time_current AS processing_time FROM [sysutility_ucp_staging].[latest_volumes] SET @row_count = @@ROWCOUNT; SET @task_elapsed_ms = DATEDIFF (ms, @task_start_time, GETUTCDATE()); RAISERROR ('Insert into [volumes_internal]: %d rows, %d ms', 0, 1, @row_count, @task_elapsed_ms); SET @task_start_time = GETUTCDATE(); INSERT INTO [sysutility_ucp_core].[smo_servers_internal] ( [urn] , [powershell_path] , [processing_time] , [batch_time] , [AuditLevel] , [BackupDirectory] , [BrowserServiceAccount] , [BrowserStartMode] , [BuildClrVersionString] , [BuildNumber] , [Collation] , [CollationID] , [ComparisonStyle] , [ComputerNamePhysicalNetBIOS] , [DefaultFile] , [DefaultLog] , [Edition] , [EngineEdition] , [ErrorLogPath] , [FilestreamShareName] , [InstallDataDirectory] , [InstallSharedDirectory] , [InstanceName] , [IsCaseSensitive] , [IsClustered] , [IsFullTextInstalled] , [IsSingleUser] , [Language] , [MailProfile] , [MasterDBLogPath] , [MasterDBPath] , [MaxPrecision] , [Name] , [NamedPipesEnabled] , [NetName] , [NumberOfLogFiles] , [OSVersion] , [PerfMonMode] , [PhysicalMemory] , [Platform] , [Processors] , [ProcessorUsage] , [Product] , [ProductLevel] , [ResourceVersionString] , [RootDirectory] , [ServerType] , [ServiceAccount] , [ServiceInstanceId] , [ServiceName] , [ServiceStartMode] , [SqlCharSet] , [SqlCharSetName] , [SqlDomainGroup] , [SqlSortOrder] , [SqlSortOrderName] , [Status] , [TapeLoadWaitTime] , [TcpEnabled] , [VersionMajor] , [VersionMinor] , [VersionString] ) SELECT urn , CONVERT(NVARCHAR(MAX), [powershell_path]) AS [powershell_path] , @processing_time_current AS [processing_time] -- $FIXED: SQLBUVSTS-316258 , SWITCHOFFSET (batch_time, DATENAME(TZoffset, SYSDATETIMEOFFSET())) AS [batch_time] , CONVERT(SMALLINT,[AuditLevel]) AS [AuditLevel] , CONVERT(NVARCHAR(260) ,[BackupDirectory]) AS [BackupDirectory] , CONVERT(NVARCHAR(128) ,[BrowserServiceAccount]) AS [BrowserServiceAccount] , CONVERT(SMALLINT,[BrowserStartMode]) AS [BrowserStartMode] , CONVERT(NVARCHAR(20) ,[BuildClrVersionString]) AS [BuildClrVersionString] , CONVERT(INT,[BuildNumber]) AS [BuildNumber] , CONVERT(NVARCHAR(128),[Collation]) AS [Collation] , CONVERT(INT,[CollationID]) AS [CollationID] , CONVERT(INT,[ComparisonStyle]) AS [ComparisonStyle] , CONVERT(NVARCHAR(128),[ComputerNamePhysicalNetBIOS]) AS [ComputerNamePhysicalNetBIOS] , CONVERT(NVARCHAR(260),[DefaultFile]) AS [DefaultFile] , CONVERT(NVARCHAR(260),[DefaultLog]) AS [DefaultLog] , CONVERT(NVARCHAR(64),[Edition]) AS [Edition] , CONVERT(SMALLINT,[EngineEdition]) AS [EngineEdition] , CONVERT(NVARCHAR(260) ,[ErrorLogPath]) AS [ErrorLogPath] , CONVERT(NVARCHAR(260) ,[FilestreamShareName]) AS [FilestreamShareName] , CONVERT(NVARCHAR(260) ,[InstallDataDirectory]) AS [InstallDataDirectory] , CONVERT(NVARCHAR(260) ,[InstallSharedDirectory]) AS [InstallSharedDirectory] , CONVERT(NVARCHAR(128) ,[InstanceName]) AS [InstanceName] , CONVERT(BIT,[IsCaseSensitive]) AS [IsCaseSensitive] , CONVERT(BIT,[IsClustered]) AS [IsClustered] , CONVERT(BIT,[IsFullTextInstalled]) AS [IsFullTextInstalled] , CONVERT(BIT,[IsSingleUser]) AS [IsSingleUser] , CONVERT(NVARCHAR(64) ,[Language]) AS [Language] , CONVERT(NVARCHAR(128),[MailProfile]) AS [MailProfile] , CONVERT(NVARCHAR(260),[MasterDBLogPath]) AS [MasterDBLogPath] , CONVERT(NVARCHAR(260),[MasterDBPath]) AS [MasterDBPath] , CONVERT(TINYINT,[MaxPrecision]) AS [MaxPrecision] , CONVERT(NVARCHAR(128) ,[Name]) AS [Name] , CONVERT(BIT,[NamedPipesEnabled]) AS [NamedPipesEnabled] , CONVERT(NVARCHAR(128) ,[NetName]) AS [NetName] , CONVERT(INT,[NumberOfLogFiles]) AS [NumberOfLogFiles] , CONVERT(NVARCHAR(32) ,[OSVersion]) AS [OSVersion] , CONVERT(SMALLINT,[PerfMonMode]) AS [PerfMonMode] , CONVERT(INT,[PhysicalMemory]) AS [PhysicalMemory] , CONVERT(NVARCHAR(32) ,[Platform]) AS [Platform] , CONVERT(SMALLINT,[Processors]) AS [Processors] , CONVERT(INT,[ProcessorUsage]) AS [ProcessorUsage] , CONVERT(NVARCHAR(48) ,[Product]) AS [Product] , CONVERT(NVARCHAR(32) ,[ProductLevel]) AS [ProductLevel] , CONVERT(NVARCHAR(32) ,[ResourceVersionString]) AS [ResourceVersionString] , CONVERT(NVARCHAR(260) ,[RootDirectory]) AS [RootDirectory] , CONVERT(SMALLINT,[ServerType]) AS [ServerType] , CONVERT(NVARCHAR(128),[ServiceAccount]) AS [ServiceAccount] , CONVERT(NVARCHAR(64),[ServiceInstanceId]) AS [ServiceInstanceId] , CONVERT(NVARCHAR(64),[ServiceName]) AS [ServiceName] , CONVERT(SMALLINT,[ServiceStartMode]) AS [ServiceStartMode] , CONVERT(SMALLINT,[SqlCharSet]) AS [SqlCharSet] , CONVERT(NVARCHAR(32),[SqlCharSetName]) AS [SqlCharSetName] , CONVERT(NVARCHAR(128),[SqlDomainGroup]) AS [SqlDomainGroup] , CONVERT(SMALLINT,[SqlSortOrder]) AS [SqlSortOrder] , CONVERT(NVARCHAR(64),[SqlSortOrderName]) AS [SqlSortOrderName] , CONVERT(SMALLINT,[Status]) AS [Status] , CONVERT(INT,[TapeLoadWaitTime]) AS [TapeLoadWaitTime] , CONVERT(BIT,[TcpEnabled]) AS [TcpEnabled] , CONVERT(INT,[VersionMajor]) AS [VersionMajor] , CONVERT(INT,[VersionMinor]) AS [VersionMinor] , CONVERT(NVARCHAR(32),[VersionString]) AS [VersionString] FROM (SELECT urn, property_name, property_value, batch_time FROM [sysutility_ucp_staging].[latest_smo_properties] WHERE object_type = 1) props -- object_type = 1 is Server PIVOT ( MAX(property_value) FOR property_name IN ( [powershell_path] , [AuditLevel] , [BackupDirectory] , [BrowserServiceAccount] , [BrowserStartMode] , [BuildClrVersionString] , [BuildNumber] , [Collation] , [CollationID] , [ComparisonStyle] , [ComputerNamePhysicalNetBIOS] , [DefaultFile] , [DefaultLog] , [Edition] , [EngineEdition] , [ErrorLogPath] , [FilestreamShareName] , [InstallDataDirectory] , [InstallSharedDirectory] , [InstanceName] , [IsCaseSensitive] , [IsClustered] , [IsFullTextInstalled] , [IsSingleUser] , [Language] , [MailProfile] , [MasterDBLogPath] , [MasterDBPath] , [MaxPrecision] , [Name] , [NamedPipesEnabled] , [NetName] , [NumberOfLogFiles] , [OSVersion] , [PerfMonMode] , [PhysicalMemory] , [Platform] , [Processors] , [ProcessorUsage] , [Product] , [ProductLevel] , [ResourceVersionString] , [RootDirectory] , [ServerType] , [ServiceAccount] , [ServiceInstanceId] , [ServiceName] , [ServiceStartMode] , [SqlCharSet] , [SqlCharSetName] , [SqlDomainGroup] , [SqlSortOrder] , [SqlSortOrderName] , [Status] , [TapeLoadWaitTime] , [TcpEnabled] , [VersionMajor] , [VersionMinor] , [VersionString] ) ) AS pvt SET @row_count = @@ROWCOUNT; SET @task_elapsed_ms = DATEDIFF (ms, @task_start_time, GETUTCDATE()); RAISERROR ('Insert into [smo_servers_internal]: %d rows, %d ms', 0, 1, @row_count, @task_elapsed_ms); SET @task_start_time = GETUTCDATE(); INSERT INTO [sysutility_ucp_core].[databases_internal] ([urn] , [powershell_path] , [processing_time] , [batch_time] , [server_instance_name] , [parent_urn] , [Collation] , [CompatibilityLevel] , [CreateDate] , [EncryptionEnabled] , [Name] , [RecoveryModel] , [Trustworthy] , [state]) SELECT [urn] , CONVERT(NVARCHAR(MAX), [powershell_path]) AS [powershell_path] , @processing_time_current AS processing_time -- $FIXED: SQLBUVSTS-316258 , SWITCHOFFSET ([batch_time], DATENAME(TZoffset, SYSDATETIMEOFFSET())) AS [batch_time] , [server_instance_name] , Left(urn, CHARINDEX('/Database[', urn, 1)-1) AS parent_urn , CONVERT(NVARCHAR(128),[Collation]) AS Collation , CONVERT(SMALLINT,[CompatibilityLevel]) AS CompatibilityLevel -- DC (SSIS) doesn't support sql_variant, so all properties are uploaded as nvarchar(4000). To successfully round-trip -- the property values through nvarchar, we use the same language-independent conversion style on MI and UCP. The shared -- fn_sysutility_get_culture_invariant_conversion_style_internal function gives us a consistent conversion style for each -- property data type that is language-independent and that won't cause data loss. We also use this function on the MI -- when converting to nvarchar so that the two conversions are symmetrical. (Ref: VSTS 361531, 359504, 12967) , CONVERT(DATETIME, [CreateDate], msdb.dbo.fn_sysutility_get_culture_invariant_conversion_style_internal('datetime')) AS CreateDate , CONVERT(BIT,[EncryptionEnabled]) AS EncryptionEnabled , CONVERT(SYSNAME,[Name])AS [Name] , CONVERT(SMALLINT,[RecoveryModel]) AS RecoveryModel , CONVERT(BIT,[Trustworthy]) AS Trustworthy -- Default to 0 (available) state. We'll update this to 1 (not available) for emergency/offline/etc databases later (we -- need to examine file and filegroup properties to infer whether a database should be available or not available). , 0 AS state FROM (SELECT urn, server_instance_name, property_name, property_value, batch_time FROM [sysutility_ucp_staging].[latest_smo_properties] WHERE object_type = 2) props -- object_type = 1 is Database PIVOT ( MAX(property_value) FOR property_name IN ( [powershell_path] , [ID] , [Collation] , [CompatibilityLevel] , [CreateDate] , [EncryptionEnabled] , [Name] , [RecoveryModel] , [Trustworthy] ) ) AS pvt SET @row_count = @@ROWCOUNT; SET @task_elapsed_ms = DATEDIFF (ms, @task_start_time, GETUTCDATE()); RAISERROR ('Insert into [databases_internal]: %d rows, %d ms', 0, 1, @row_count, @task_elapsed_ms); SET @task_start_time = GETUTCDATE(); INSERT INTO [sysutility_ucp_core].[filegroups_internal] ([urn] , [powershell_path] , [processing_time] , [batch_time] , [server_instance_name] , [database_name] , [parent_urn] , [Name]) SELECT [urn] , CONVERT(NVARCHAR(MAX), [powershell_path]) AS [powershell_path] , @processing_time_current AS processing_time -- $FIXED: SQLBUVSTS-316258 , SWITCHOFFSET ([batch_time], DATENAME(TZoffset, SYSDATETIMEOFFSET())) AS [batch_time] , [server_instance_name] , CONVERT(SYSNAME,[parent_name]) AS [database_name] , Left(urn, CHARINDEX('/FileGroup[', urn, 1)-1) AS parent_urn , CONVERT(SYSNAME,[Name]) AS Name FROM (SELECT urn, server_instance_name, property_name, property_value, batch_time FROM [sysutility_ucp_staging].[latest_smo_properties] WHERE object_type = 4) props -- object_type = 4 is FileGroup PIVOT ( MAX(property_value) FOR property_name IN ( [powershell_path] , [parent_name] , [ID] , [Name]) ) AS pvt SET @row_count = @@ROWCOUNT; SET @task_elapsed_ms = DATEDIFF (ms, @task_start_time, GETUTCDATE()); RAISERROR ('Insert into [filegroups_internal]: %d rows, %d ms', 0, 1, @row_count, @task_elapsed_ms); SET @task_start_time = GETUTCDATE(); INSERT INTO [sysutility_ucp_core].[datafiles_internal] ([urn] , [powershell_path] , [processing_time] , [batch_time] , [server_instance_name] , [database_name] , [filegroup_name] , [parent_urn] , [Growth] , [GrowthType] , [MaxSize] , [Name] , [Size] , [UsedSpace] , [FileName] , [VolumeFreeSpace] , [volume_name] , [volume_device_id] , [physical_server_name]) SELECT [urn] , CONVERT(NVARCHAR(MAX), pvt.[powershell_path]) AS [powershell_path] , @processing_time_current AS processing_time -- $FIXED: SQLBUVSTS-316258 , SWITCHOFFSET (pvt.[batch_time], DATENAME(TZoffset, SYSDATETIMEOFFSET())) AS [batch_time] , pvt.[server_instance_name] , CONVERT(SYSNAME,[grandparent_name]) AS [database_name] , CONVERT(SYSNAME,[parent_name]) AS [filegroup_name] , Left(urn, CHARINDEX('/File[', urn, 1)-1) AS parent_urn , CONVERT(REAL,[Growth]) AS Growth , CONVERT(SMALLINT,[GrowthType]) AS GrowthType , CONVERT(REAL,[MaxSize]) AS MaxSize , CONVERT(SYSNAME,[Name]) AS Name , CONVERT(REAL,[Size]) AS Size , CONVERT(REAL,[UsedSpace]) AS UsedSpace , CONVERT(NVARCHAR(260),[FileName]) AS FileName , ISNULL(v.free_space, 0.0) * 1024 AS VolumeFreeSpace -- volumes_internal.free_space is MB, and VolumeFreeSpace is expected to be KB. , ISNULL(v.volume_name, N'') AS volume_name , v.[volume_device_id] AS [volume_device_id] , pvt.[physical_server_name] FROM (SELECT urn, physical_server_name, server_instance_name, property_name, property_value, batch_time FROM [sysutility_ucp_staging].[latest_smo_properties] WHERE object_type = 5) props -- object_type = 5 is DataFile PIVOT ( MAX(property_value) FOR property_name IN ( [powershell_path] , [parent_name] , [grandparent_name] , [ID] , [Growth] , [GrowthType] , [MaxSize] , [Name] , [Size] , [UsedSpace] , [FileName] , [volume_device_id]) ) AS pvt LEFT OUTER JOIN [sysutility_ucp_core].[volumes_internal] v ON v.physical_server_name = pvt.physical_server_name AND CONVERT(SYSNAME, pvt.[volume_device_id]) = v.volume_device_id WHERE v.processing_time = @processing_time_current SET @row_count = @@ROWCOUNT; SET @task_elapsed_ms = DATEDIFF (ms, @task_start_time, GETUTCDATE()); RAISERROR ('Insert into [datafiles_internal]: %d rows, %d ms', 0, 1, @row_count, @task_elapsed_ms); SET @task_start_time = GETUTCDATE(); INSERT INTO [sysutility_ucp_core].[logfiles_internal] ([urn] , [powershell_path] , [processing_time] , [batch_time] , [server_instance_name] , [database_name] , [parent_urn] , [Growth] , [GrowthType] , [MaxSize] , [Name] , [Size] , [UsedSpace] , [FileName] , [VolumeFreeSpace] , [volume_name] , [volume_device_id] , [physical_server_name]) SELECT[urn] , CONVERT(NVARCHAR(MAX), pvt.[powershell_path]) AS [powershell_path] , @processing_time_current AS processing_time -- $FIXED: SQLBUVSTS-316258 , SWITCHOFFSET (pvt.[batch_time], DATENAME(TZoffset, SYSDATETIMEOFFSET())) AS [batch_time] , pvt.[server_instance_name] , CONVERT(SYSNAME,[parent_name]) AS [database_name] , Left(urn, CHARINDEX('/LogFile[', urn, 1)-1) AS parent_urn , CONVERT(REAL,[Growth]) AS Growth , CONVERT(SMALLINT,[GrowthType]) AS GrowthType , CONVERT(REAL,[MaxSize]) AS MaxSize , CONVERT(SYSNAME,[Name]) AS Name , CONVERT(REAL,[Size]) AS Size --- The collection data may not contain the UsedSpace property of the log file of a database in EMERGENCY state. , ISNULL(CONVERT(REAL,[UsedSpace]), 0.0) AS UsedSpace , CONVERT(NVARCHAR(260),[FileName]) AS FileName , ISNULL(v.free_space, 0.0) * 1024 AS VolumeFreeSpace -- volumes_internal.free_space is MB, and VolumeFreeSpace is expected to be KB. , ISNULL(v.volume_name, N'') AS volume_name , v.[volume_device_id] AS [volume_device_id] , pvt.[physical_server_name] FROM (SELECT urn, physical_server_name, server_instance_name, property_name, property_value, batch_time FROM [sysutility_ucp_staging].[latest_smo_properties] WHERE object_type = 3) props -- object_type = 3 is LogFile PIVOT ( MAX(property_value) FOR property_name IN ( [powershell_path] , [parent_name] , [ID] , [Growth] , [GrowthType] , [MaxSize] , [Name] , [Size] , [UsedSpace] , [FileName] , [volume_device_id]) ) AS pvt LEFT OUTER JOIN [sysutility_ucp_core].[volumes_internal] v ON v.physical_server_name = pvt.physical_server_name AND CONVERT(SYSNAME, pvt.[volume_device_id]) = v.volume_device_id WHERE v.processing_time = @processing_time_current SET @row_count = @@ROWCOUNT; SET @task_elapsed_ms = DATEDIFF (ms, @task_start_time, GETUTCDATE()); RAISERROR ('Insert into [logfiles_internal]: %d rows, %d ms', 0, 1, @row_count, @task_elapsed_ms); SET @task_start_time = GETUTCDATE(); -- Identify all of the databases for which we do not have all file/filegroup details because of the current -- database status (db is offline, recovering, emergency mode, etc). Update the state for these databases -- to 1 ("not available"). UPDATE sysutility_ucp_core.databases_internal SET state = 1 FROM sysutility_ucp_core.databases_internal AS db WHERE -- Case #1: Emergency mode databases are considered 'not available' -- the database is not recovered, and -- we can't get correct log file metadata. We detect this by looking for databases with a log file that -- has a size of 0, which is impossible in an available database. EXISTS ( SELECT * FROM sysutility_ucp_core.logfiles_internal AS lf WHERE lf.server_instance_name = db.server_instance_name AND lf.database_name = db.Name AND lf.Size = 0 AND lf.processing_time = @processing_time_current) -- Case #2: When a database is in other "not available" states (like recovering, offline, suspect), we -- cannot retrieve filegroup or file-level metadata. We detect this case by looking for databases that seem -- to have no filegroups, which is an impossible state for an online & available database. OR NOT EXISTS ( SELECT * FROM sysutility_ucp_core.filegroups_internal AS fg WHERE fg.server_instance_name = db.server_instance_name AND fg.database_name = db.Name AND fg.processing_time = @processing_time_current); SET @row_count = @@ROWCOUNT; SET @task_elapsed_ms = DATEDIFF (ms, @task_start_time, GETUTCDATE()); RAISERROR ('Discover unavailable databases: %d rows, %d ms', 0, 1, @row_count, @task_elapsed_ms); SET @task_start_time = GETUTCDATE(); ----- ----- Stage 2: Insert into all the measure tables ----- cpu_utilization_internal (computers, instances, dacs) ----- space_utilization_internal (volumes, instances, databases, filegroups, datafiles, logfiles) ----- ----- INSERT INTO [sysutility_ucp_core].[cpu_utilization_internal]( aggregation_type, object_type, processing_time, physical_server_name, server_instance_name, database_name, percent_total_cpu_utilization) SELECT 0, -- No aggregation 1, -- Computer Object @processing_time_current, physical_server_name, N'', N'', percent_total_cpu_utilization FROM [sysutility_ucp_core].[computers_internal] WHERE processing_time = @processing_time_current UNION ALL SELECT 0, -- No aggregation 3, -- Instance object @processing_time_current, N'', server_instance_name, N'', instance_processor_usage FROM [sysutility_ucp_staging].[latest_instance_cpu_utilization] UNION ALL SELECT 0, -- No aggregation 4, -- Database/DAC object @processing_time_current, N'', -- computer_name server_instance_name, dac_name, dac_percent_total_cpu_utilization FROM [sysutility_ucp_core].[dacs_internal] WHERE processing_time = @processing_time_current SET @row_count = @@ROWCOUNT; SET @task_elapsed_ms = DATEDIFF (ms, @task_start_time, GETUTCDATE()); RAISERROR ('Insert into [cpu_utilization_internal]: %d rows, %d ms', 0, 1, @row_count, @task_elapsed_ms); SET @task_start_time = GETUTCDATE(); INSERT INTO [sysutility_ucp_core].[space_utilization_internal] ( aggregation_type, object_type, processing_time, virtual_server_name, volume_device_id, server_instance_name, database_name, [filegroup_name], dbfile_name, total_space_bytes, allocated_space_bytes, used_space_bytes, available_space_bytes) SELECT 0 AS aggregation_type, CASE WHEN group_id = 0 AND [filegroup_name] = N'' THEN 7 -- logfile WHEN group_id = 0 THEN 6 -- datafile WHEN group_id = 1 THEN 5 -- filegroup WHEN group_id = 3 THEN 4 -- database WHEN group_id = 7 THEN 3 -- instance ELSE NULL -- should never get here END as [object_type], @processing_time_current AS processing_time, N'' as virtual_server_name, N'' as volume_device_id, ISNULL(server_instance_name, N''), -- shouldn't ever get to be null ISNULL(database_name, N''), ISNULL([filegroup_name], N''), ISNULL([dbfile_name], N''), CASE WHEN group_id = 0 THEN total_space_kb * 1024 ELSE NULL END, CASE WHEN group_id = 0 THEN allocated_space_kb * 1024 ELSE NULL END, used_space_kb * 1024, CASE WHEN group_id = 0 THEN available_space_kb * 1024 ELSE NULL END FROM ( SELECT server_instance_name, database_name, [filegroup_name], dbfile_name, SUM(MaxSize) AS total_space_kb, -- Is this right? SUM([Size]) AS allocated_space_kb, SUM(UsedSpace) AS used_space_kb, SUM(available_space) AS available_space_kb, GROUPING_ID(server_instance_name, database_name, [filegroup_name], dbfile_name) AS group_id FROM (SELECT server_instance_name, database_name, [filegroup_name], [Name] as dbfile_name, MaxSize, [Size], UsedSpace, [sysutility_ucp_misc].[fn_get_max_size_available]([Size], [MaxSize], [Growth], [GrowthType], [VolumeFreeSpace]) AS available_space FROM [sysutility_ucp_core].[datafiles_internal] WHERE processing_time = @processing_time_current UNION ALL SELECT server_instance_name, database_name, N'' AS [filegroup_name], [Name] AS dbfile_name, MaxSize, [Size], UsedSpace, [sysutility_ucp_misc].[fn_get_max_size_available]([Size], [MaxSize], [Growth], [GrowthType], [VolumeFreeSpace]) AS available_space FROM [sysutility_ucp_core].[logfiles_internal] WHERE processing_time = @processing_time_current) as dbfiles GROUP BY GROUPING SETS((server_instance_name), (server_instance_name, database_name), (server_instance_name, database_name, [filegroup_name]), (server_instance_name, database_name, [filegroup_name], [dbfile_name]) ) ) AS instance_space_utilizations UNION ALL SELECT 0 AS aggregation_type, CASE WHEN GROUPING_ID(virtual_server_name, volume_device_id) = 3 THEN 0 -- utility WHEN GROUPING_ID(virtual_server_name, volume_device_id) = 1 THEN 1 -- computer WHEN GROUPING_ID(virtual_server_name, volume_device_id) = 0 THEN 2 -- volume ELSE NULL -- should never get here END AS object_type, @processing_time_current as processing_time, ISNULL(virtual_server_name, N''), ISNULL(volume_device_id, N'') AS volume_device_id, N'' as server_instance_name, N'' as database_name, N'' as [filegroup_name], N'' as dbfile_name, SUM(total_space_available)*1048576 AS total_space_bytes, SUM(total_space_available)*1048576 AS allocated_space_bytes, SUM(total_space_available - free_space)*1048576 AS used_space_bytes, SUM(free_space)*1048576 AS available_space_bytes FROM [sysutility_ucp_core].[volumes_internal] WHERE processing_time = @processing_time_current GROUP BY GROUPING SETS ((), (virtual_server_name), (virtual_server_name, volume_device_id) ) SET @row_count = @@ROWCOUNT; SET @task_elapsed_ms = DATEDIFF (ms, @task_start_time, GETUTCDATE()); RAISERROR ('Insert into [space_utilization_internal]: %d rows, %d ms', 0, 1, @row_count, @task_elapsed_ms); -- -- State changes -- UPDATE [msdb].[dbo].[sysutility_ucp_processing_state_internal] SET latest_processing_time = @processing_time_current -- Update the snapshot partitions table -- Push down the previous snapshot partition values and -- store the current max snapshot in the latest (top) record UPDATE [msdb].[dbo].[sysutility_ucp_snapshot_partitions_internal] SET latest_consistent_snapshot_id = (SELECT TOP 1 latest_consistent_snapshot_id FROM [msdb].[dbo].[sysutility_ucp_snapshot_partitions_internal] WHERE time_id = 1) WHERE time_id = 2 UPDATE [msdb].[dbo].[sysutility_ucp_snapshot_partitions_internal] SET latest_consistent_snapshot_id = (SELECT TOP 1 latest_consistent_snapshot_id FROM [msdb].[dbo].[sysutility_ucp_snapshot_partitions_internal] WHERE time_id = 0) WHERE time_id = 1 UPDATE [msdb].[dbo].[sysutility_ucp_snapshot_partitions_internal] SET latest_consistent_snapshot_id = @max_snapshot_id WHERE time_id = 0 -- As we have inserted chunk of data in the cache tables, the stats on these tables -- get stale there by leading to performance degradation of the health state queries -- Force update the stats on these tables so that the QO is able to generate a more -- realisitc query execution plan SET @task_start_time = GETUTCDATE(); UPDATE STATISTICS [msdb].[dbo].[sysutility_ucp_processing_state_internal]; -- Update stats on all dimension and measure cache tables DECLARE @schema sysname DECLARE @name sysname DECLARE @query NVARCHAR(MAX) DECLARE cache_tables CURSOR FOR SELECT object_schema, [object_name] FROM sysutility_ucp_misc.utility_objects_internal WHERE utility_object_type IN ('DIMENSION', 'MEASURE'); OPEN cache_tables; FETCH NEXT FROM cache_tables INTO @schema, @name; WHILE (@@FETCH_STATUS <> -1) BEGIN SET @query = 'UPDATE STATISTICS ' + QUOTENAME (@schema) + '.' + QUOTENAME (@name); EXEC (@query); FETCH NEXT FROM cache_tables INTO @schema, @name; END; CLOSE cache_tables; DEALLOCATE cache_tables; SET @task_elapsed_ms = DATEDIFF (ms, @task_start_time, GETUTCDATE()); RAISERROR ('Update statistics: %d ms', 0, 1, @task_elapsed_ms); END GO EXEC sp_addextendedproperty @name = 'MS_UtilityObjectType', @value = 'STAGING', @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_staging', @level1type = 'PROCEDURE', @level1name = 'sp_copy_live_table_data_into_cache_tables'; GO ----------------------------------------------------------------------------------------- -- Procedure sp_copy_cache_table_data_into_aggregate_tables -- Aggregates the latest round of data from the "cache" tables into the "aggregate" tables -- for the appropriate aggregation interval ----------------------------------------------------------------------------------------- IF OBJECT_ID ('sysutility_ucp_core.sp_copy_cache_table_data_into_aggregate_tables') IS NOT NULL BEGIN RAISERROR('Dropping procedure sysutility_ucp_core.sp_copy_cache_table_data_into_aggregate_tables procedure', 0, 1) WITH NOWAIT; DROP PROCEDURE sysutility_ucp_core.sp_copy_cache_table_data_into_aggregate_tables; END GO RAISERROR('Creating procedure sysutility_ucp_core.sp_copy_cache_table_data_into_aggregate_tables', 0, 1) WITH NOWAIT; GO CREATE PROCEDURE sysutility_ucp_core.sp_copy_cache_table_data_into_aggregate_tables @aggregation_type INT, @endTime DATETIMEOFFSET(7) AS BEGIN DECLARE @startTime DATETIMEOFFSET(7) DECLARE @lowerAggregationLevel [sysutility_ucp_core].AggregationType SELECT @lowerAggregationLevel = 0 -- compute from detail rows IF (@aggregation_type = 1) SELECT @startTime = DATEADD(hour, -1, @endTime) ELSE IF (@aggregation_type = 2) SELECT @startTime = DATEADD(day, -1, @endTime) -- todo (VSTS #345038) -- Ideally, we would be using the hourly aggregation values to compute the -- daily aggregation ("SELECT @lowerAggregationLevel = 1") -- ELSE BEGIN -- todo. Raise an error instead RETURN(1) END INSERT INTO [sysutility_ucp_core].[cpu_utilization_internal] ( aggregation_type, object_type, processing_time, physical_server_name, server_instance_name, database_name, percent_total_cpu_utilization) SELECT @aggregation_type, object_type, @endTime, physical_server_name, server_instance_name, database_name, AVG(percent_total_cpu_utilization) FROM [sysutility_ucp_core].[cpu_utilization_internal] WHERE (processing_time BETWEEN @startTime and @endTime) AND aggregation_type = @lowerAggregationLevel GROUP BY object_type, physical_server_name, server_instance_name, database_name INSERT INTO [sysutility_ucp_core].[space_utilization_internal] ( aggregation_type, object_type, processing_time, virtual_server_name, volume_device_id, server_instance_name, database_name, [filegroup_name], dbfile_name, total_space_bytes, allocated_space_bytes, used_space_bytes, available_space_bytes) SELECT @aggregation_type, object_type, @endTime, virtual_server_name, volume_device_id, server_instance_name, database_name, [filegroup_name], dbfile_name, -- Is AVG the right aggregate to use - should this instead be LAST() AVG(total_space_bytes), AVG(allocated_space_bytes), AVG(used_space_bytes), AVG(available_space_bytes) FROM [sysutility_ucp_core].[space_utilization_internal] WHERE (processing_time BETWEEN @startTime and @endTime) AND aggregation_type = @lowerAggregationLevel GROUP BY object_type, virtual_server_name, volume_device_id, server_instance_name, database_name, [filegroup_name], dbfile_name END GO EXEC sp_addextendedproperty @name = 'MS_UtilityObjectType', @value = 'CORE', @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_core', @level1type = 'PROCEDURE', @level1name = 'sp_copy_cache_table_data_into_aggregate_tables'; GO ----------------------------------------------------------------------------------------- -- Procedure sp_purge_cache_tables -- Deletes the data in Utility's MDW cache tables according to the data retention -- periods specified in msdb.dbo.sysutility_ucp_configuration_internal. ----------------------------------------------------------------------------------------- IF OBJECT_ID ('sysutility_ucp_core.sp_purge_cache_tables') IS NOT NULL BEGIN RAISERROR('Dropping procedure sysutility_ucp_core.sp_purge_cache_tables procedure', 0, 1) WITH NOWAIT; DROP PROCEDURE sysutility_ucp_core.sp_purge_cache_tables; END GO RAISERROR('Creating procedure sysutility_ucp_core.sp_purge_cache_tables', 0, 1) WITH NOWAIT; GO CREATE PROCEDURE sysutility_ucp_core.sp_purge_cache_tables AS BEGIN DECLARE @rows_affected bigint; DECLARE @delete_batch_size varchar(30); SET @delete_batch_size = 500; SET @rows_affected = -1; DECLARE @days_to_retain_minute_data int; DECLARE @days_to_retain_hour_data int; DECLARE @days_to_retain_day_data int; SELECT @days_to_retain_minute_data = CONVERT (int,current_value) FROM [msdb].[dbo].[sysutility_ucp_configuration_internal] WHERE name = 'MdwRetentionLengthInDaysForMinutesHistory'; SELECT @days_to_retain_hour_data = CONVERT (int,current_value) FROM [msdb].[dbo].[sysutility_ucp_configuration_internal] WHERE name = 'MdwRetentionLengthInDaysForHoursHistory'; SELECT @days_to_retain_day_data = CONVERT (int,current_value) FROM [msdb].[dbo].[sysutility_ucp_configuration_internal] WHERE name = 'MdwRetentionLengthInDaysForDaysHistory'; DECLARE @date_threshold_minute_data DATETIMEOFFSET(7) = DATEADD(day, -@days_to_retain_minute_data, SYSDATETIMEOFFSET()); DECLARE @date_threshold_hour_data DATETIMEOFFSET(7) = DATEADD(day, -@days_to_retain_hour_data, SYSDATETIMEOFFSET()); DECLARE @date_threshold_day_data DATETIMEOFFSET(7) = DATEADD(day, -@days_to_retain_day_data, SYSDATETIMEOFFSET()); DECLARE @schema sysname DECLARE @name sysname DECLARE @query NVARCHAR(MAX) DECLARE dimensions_cursor CURSOR FOR SELECT object_schema, [object_name] FROM sysutility_ucp_misc.utility_objects_internal WHERE utility_object_type = 'DIMENSION'; -- Purge the dimension tables. -- The number of rows that can be deleted from these tables can be very large. If we deleted -- all of these rows in a single delete statement, we would hold locks for an arbitrarily-long -- time (and potentially escalate to table locks), causing long-duration blocking. This could -- also lead to transaction log growth, since log records after the oldest still-open transaction -- can't be truncated. To avoid these two problems, we delete rows in batches of 500 and loop -- until we've deleted all rows that we no longer need. OPEN dimensions_cursor; FETCH NEXT FROM dimensions_cursor INTO @schema, @name; WHILE (@@FETCH_STATUS <> -1) BEGIN SET @rows_affected = -1; WHILE (@rows_affected != 0) BEGIN -- We use dynamic SQL here because the table name is variable, but this also has the benefit of -- providing the optimizer with the final value for @delete_batch_size and @date_threshold. SET @query = 'DELETE TOP (' + @delete_batch_size + ') FROM ' + QUOTENAME(@schema) + '.' + QUOTENAME(@name) + ' WHERE processing_time < @date_threshold'; EXEC sp_executesql @query, N'@date_threshold datetimeoffset(7)', @date_threshold = @date_threshold_minute_data; SET @rows_affected = @@ROWCOUNT; END; FETCH NEXT FROM dimensions_cursor INTO @schema, @name; END; CLOSE dimensions_cursor; DEALLOCATE dimensions_cursor; DECLARE measures_cursor CURSOR FOR SELECT object_schema, [object_name] FROM sysutility_ucp_misc.utility_objects_internal WHERE utility_object_type = 'MEASURE'; -- Delete "per-minute" (15 minute) data from measure tables OPEN measures_cursor; FETCH NEXT FROM measures_cursor INTO @schema, @name; WHILE (@@FETCH_STATUS <> -1) BEGIN SET @rows_affected = -1; WHILE (@rows_affected != 0) BEGIN SET @query = 'DELETE TOP (' + @delete_batch_size + ') FROM ' + QUOTENAME(@schema) + '.' + QUOTENAME(@name) + ' WHERE processing_time < @date_threshold AND aggregation_type = 0'; EXEC sp_executesql @query, N'@date_threshold datetimeoffset(7)', @date_threshold = @date_threshold_minute_data; SET @rows_affected = @@ROWCOUNT; END; FETCH NEXT FROM measures_cursor INTO @schema, @name; END; CLOSE measures_cursor; -- Delete "per-hour" data from our measure-tables OPEN measures_cursor; FETCH NEXT FROM measures_cursor INTO @schema, @name; WHILE (@@FETCH_STATUS <> -1) BEGIN SET @rows_affected = -1; WHILE (@rows_affected != 0) BEGIN SET @query = 'DELETE TOP (' + @delete_batch_size + ') FROM ' + QUOTENAME(@schema) + '.' + QUOTENAME(@name) + ' WHERE processing_time < @date_threshold AND aggregation_type = 1'; EXEC sp_executesql @query, N'@date_threshold datetimeoffset(7)', @date_threshold = @date_threshold_hour_data; SET @rows_affected = @@ROWCOUNT; END; FETCH NEXT FROM measures_cursor INTO @schema, @name; END; CLOSE measures_cursor; -- Delete "per-day" data from measure tables OPEN measures_cursor; FETCH NEXT FROM measures_cursor INTO @schema, @name; WHILE (@@FETCH_STATUS <> -1) BEGIN SET @rows_affected = -1; WHILE (@rows_affected != 0) BEGIN SET @query = 'DELETE TOP (' + @delete_batch_size + ') FROM ' + QUOTENAME(@schema) + '.' + QUOTENAME(@name) + ' WHERE processing_time < @date_threshold AND aggregation_type = 2'; EXEC sp_executesql @query, N'@date_threshold datetimeoffset(7)', @date_threshold = @date_threshold_day_data; SET @rows_affected = @@ROWCOUNT; END; FETCH NEXT FROM measures_cursor INTO @schema, @name; END; CLOSE measures_cursor; DEALLOCATE measures_cursor; END; GO EXEC sp_addextendedproperty @name = 'MS_UtilityObjectType', @value = 'CORE', @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_core', @level1type = 'PROCEDURE', @level1name = 'sp_purge_cache_tables'; GO ----------------------------------------------------------------------------------------- -- Procedure sp_initialize_mdw_internal -- Performs runtime schema modifications that must be deferred until Create UCP. -- Executed by sp_sysutility_ucp_initialize_mdw in msdb. -- -- Note that this proc calls xp_qv, which requires that the 'Agent XPs' sp_configure -- value be enabled. During upgrade, this setting will need to be manually enabled, -- since upgrade scripts are executed while Agent is stopped. ----------------------------------------------------------------------------------------- IF OBJECT_ID ('sysutility_ucp_core.sp_initialize_mdw_internal') IS NOT NULL BEGIN RAISERROR('Dropping procedure sysutility_ucp_core.sp_initialize_mdw_internal procedure', 0, 1) WITH NOWAIT; DROP PROCEDURE sysutility_ucp_core.sp_initialize_mdw_internal; END GO RAISERROR('Creating procedure sysutility_ucp_core.sp_initialize_mdw_internal', 0, 1) WITH NOWAIT; GO CREATE PROCEDURE sysutility_ucp_core.sp_initialize_mdw_internal AS BEGIN IF (msdb.dbo.fn_sysutility_ucp_get_edition_is_ucp_capable_internal() = 1) BEGIN RAISERROR ('Instance is able to be used as a Utility Control Point.', 0, 1) WITH NOWAIT; END ELSE BEGIN DECLARE @edition nvarchar(128); SELECT @edition = CONVERT(nvarchar(128), SERVERPROPERTY('Edition')); RAISERROR(37004, -1, -1, @edition); RETURN(1); END; -- The Utility schema uses two Enterprise-only engine features: compression and partitioning. -- Utility is an Enterprise-only feature, but we share instmdw.sql and the MDW database with other -- features that are not Enterprise only. Therefore we can't include the following things as part -- of the CREATE TABLE statements because instmdw.sql would fail when run on a non-Enterprise SKU. -- To work around this, we defer this part of the UCP schema creation until Create UCP is run. IF (3 = CONVERT (int, SERVERPROPERTY('EngineEdition'))) -- Enterprise/Enterprise Eval/Developer/Data Center BEGIN -- Enable data compression on tables that benefit from it the most RAISERROR ('Enabling compression on MDW tables', 0, 1) WITH NOWAIT; ALTER TABLE [snapshots].[sysutility_ucp_smo_properties_internal] REBUILD WITH (DATA_COMPRESSION = PAGE); ALTER TABLE [sysutility_ucp_core].[smo_servers_internal] REBUILD WITH (DATA_COMPRESSION = PAGE); ALTER TABLE [sysutility_ucp_core].[databases_internal] REBUILD WITH (DATA_COMPRESSION = PAGE); ALTER TABLE [sysutility_ucp_core].[filegroups_internal] REBUILD WITH (DATA_COMPRESSION = PAGE); ALTER TABLE [sysutility_ucp_core].[datafiles_internal] REBUILD WITH (DATA_COMPRESSION = PAGE); ALTER TABLE [sysutility_ucp_core].[logfiles_internal] REBUILD WITH (DATA_COMPRESSION = PAGE); ALTER TABLE [sysutility_ucp_core].[cpu_utilization_internal] REBUILD WITH (DATA_COMPRESSION = PAGE); ALTER TABLE [sysutility_ucp_core].[space_utilization_internal] REBUILD WITH (DATA_COMPRESSION = PAGE); -- Partitioning (and compression) on measure tables -- Create a partitioning scheme based on aggregationType. In effect, we'd like -- each aggregation-level to get its own partition. Currently, we only support -- 3 aggregation levels. -- -- FUTURE: It would be nice to support composite partitioning in the future. -- 1. We could further create a sub-partition for each object-type (computer etc.) -- 2. If we could have sliding partitions, we could avoid deletes altogether, and -- instead work with truncate/drop-partition style operations -- IF NOT EXISTS (SELECT name FROM sys.partition_functions WHERE name = N'sysutility_ucp_aggregation_type_partition_function') BEGIN RAISERROR ('Creating partition function [sysutility_ucp_aggregation_type_partition_function]', 0, 1) WITH NOWAIT; -- Use dynamic SQL here (and in the next create stmt) b/c otherwise SQL will fail the creation of this -- proc on Workgroup or Standard edition. EXEC (' CREATE PARTITION FUNCTION [sysutility_ucp_aggregation_type_partition_function](TINYINT) AS RANGE LEFT FOR VALUES(0, 1, 2)'); END; IF NOT EXISTS (SELECT name FROM sys.partition_schemes WHERE name = N'sysutility_ucp_aggregation_type_partition_scheme') BEGIN RAISERROR ('Creating partition scheme [sysutility_ucp_aggregation_type_partition_scheme]', 0, 1) WITH NOWAIT; EXEC (' CREATE PARTITION SCHEME [sysutility_ucp_aggregation_type_partition_scheme] AS PARTITION [sysutility_ucp_aggregation_type_partition_function] ALL TO ([PRIMARY])'); END; -- ALTER INDEX can't change partition scheme. Instead we must drop and recreate the clustered PKs. IF OBJECT_ID ('[sysutility_ucp_core].[pk_cpu_utilization_internal]') IS NOT NULL BEGIN RAISERROR ('Dropping primary key [sysutility_ucp_core].[cpu_utilization_internal].[pk_cpu_utilization_internal]', 0, 1) WITH NOWAIT; ALTER TABLE [sysutility_ucp_core].[cpu_utilization_internal] DROP CONSTRAINT [pk_cpu_utilization_internal]; END; RAISERROR ('Creating partitioned primary key [sysutility_ucp_core].[cpu_utilization_internal].[pk_cpu_utilization_internal]', 0, 1) WITH NOWAIT; ALTER TABLE [sysutility_ucp_core].[cpu_utilization_internal] ADD CONSTRAINT pk_cpu_utilization_internal PRIMARY KEY CLUSTERED (aggregation_type, processing_time, object_type, physical_server_name, server_instance_name, database_name) WITH (DATA_COMPRESSION = PAGE) ON [sysutility_ucp_aggregation_type_partition_scheme](aggregation_type); IF OBJECT_ID ('[sysutility_ucp_core].[pk_storage_utilization]') IS NOT NULL BEGIN RAISERROR ('Dropping primary key [sysutility_ucp_core].[space_utilization_internal].[pk_storage_utilization]', 0, 1) WITH NOWAIT; ALTER TABLE [sysutility_ucp_core].[space_utilization_internal] DROP CONSTRAINT [pk_storage_utilization]; END; RAISERROR ('Creating partitioned primary key [sysutility_ucp_core].[space_utilization_internal].[pk_storage_utilization]', 0, 1) WITH NOWAIT; ALTER TABLE [sysutility_ucp_core].[space_utilization_internal] ADD CONSTRAINT pk_storage_utilization PRIMARY KEY CLUSTERED( aggregation_type, processing_time, object_type, virtual_server_name, volume_device_id, server_instance_name, database_name, [filegroup_name], dbfile_name) WITH (DATA_COMPRESSION = PAGE) ON [sysutility_ucp_aggregation_type_partition_scheme](aggregation_type); END; END; GO EXEC sp_addextendedproperty @name = 'MS_UtilityObjectType', @value = 'CORE', @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_core', @level1type = 'PROCEDURE', @level1name = 'sp_initialize_mdw_internal'; GO BEGIN TRANSACTION ----------------------------------------------------------------------- -- Remove the sysutility_get_views_data_into_cache_tables job if it is already existing. ----------------------------------------------------------------------- DECLARE @job_id UNIQUEIDENTIFIER SELECT @job_id = job_id FROM msdb.dbo.sysjobs_view WHERE name = N'sysutility_get_views_data_into_cache_tables' IF EXISTS (SELECT job_id FROM msdb.dbo.sysjobs_view WHERE name = N'sysutility_get_views_data_into_cache_tables') EXEC msdb.dbo.sp_delete_job @job_id=@job_id, @delete_unused_schedule=1 DECLARE @ReturnCode INT -- Get the current logged in user name. DECLARE @CurrentLoggedIn nvarchar(128) Set @CurrentLoggedIn=SYSTEM_USER SELECT @ReturnCode = 0 ----------------------------------------------------------------------- -- 'sysutility_get_views_data_into_cache_tables job' has following steps: -- 1. Insert [sysutility_ucp_core].[fn_get_cpu_utilizations](0) data into -- [sysutility_ucp_core].[computer_cpu_utilizations_internal] cache table. -- 2. Insert [snapshots].[dac_view] data into -- [snapshots].[dac_table] -- 3. Insert storage live information into -- storage cache table -- 4. Insert SMO live information into -- SMO cache table ----------------------------------------------------------------------- IF NOT EXISTS (SELECT name FROM msdb.dbo.syscategories WHERE name=N'[Uncategorized (Local)]' AND category_class=1) BEGIN EXEC @ReturnCode = msdb.dbo.sp_add_category @class=N'JOB', @type=N'LOCAL', @name=N'[Uncategorized (Local)]' IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback END DECLARE @mdwDBName NVARCHAR(256) SELECT @mdwDBName = DB_NAME() DECLARE @jobId BINARY(16) EXEC @ReturnCode = msdb.dbo.sp_add_job @job_name=N'sysutility_get_views_data_into_cache_tables', @enabled=1, @notify_level_eventlog=0, @notify_level_email=0, @notify_level_netsend=0, @notify_level_page=0, @delete_level=0, @description=N'Gets all the views data into corresponding cache tables after every 15 minutes', @category_name=N'[Uncategorized (Local)]', @owner_login_name= @CurrentLoggedIn , @job_id = @jobId OUTPUT IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id=@jobId, @step_name=N'Insert latest data from live tables into cache tables', @step_id=1, @cmdexec_success_code=0, @on_success_action=3, @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 [sysutility_ucp_staging].sp_copy_live_table_data_into_cache_tables', @database_name = @mdwDBName, @flags=0 DECLARE @execute_policy_script NVARCHAR(MAX) -- Powershell script to evaluate utility resource health policies -- Note 1: The following line uses SQL Agent tokens to set the server name -- ESCAPE_SQUOTE(SRVR) with a $ sign in front is a special token to SQL Agent -- When the job is run, SQL Agent will expand the string to the server name -- Use single quotes so that PS considers the string a literal and will not -- try to expand the $ reference and the script will not fail in a test environment -- Note 2: the current approach filters on policy name to identify the resource health policy -- this might turn out to be an issue if we start localizing the resource health policies. SET @execute_policy_script = N'$serverName = ''$(ESCAPE_SQUOTE(SRVR))''; $path = Convert-UrnToPath "PolicyStore[@Name=`''$serverName`'']"; dir $path\Policies -FORCE | where { $_.IsSystemObject -eq $true -and $_.Name -like ''Utility*'' } | Invoke-PolicyEvaluation -TargetServerName $serverName;' EXEC msdb.dbo.sp_add_jobstep @job_id=@jobId, @step_name=N'Execute resource health policy evaluation job', @step_id=2, @cmdexec_success_code=0, @on_success_action=3, @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'PowerShell', @command=@execute_policy_script, @database_name=N'msdb', @flags=0 EXEC msdb.dbo.sp_add_jobstep @job_id=@jobId, @step_name=N'Compute resource health states', @step_id=3, @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 msdb.dbo.sp_sysutility_ucp_calculate_health', @database_name=N'msdb', @flags=0 EXEC @ReturnCode = msdb.dbo.sp_update_job @job_id = @jobId, @start_step_id = 1 IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback EXEC @ReturnCode = msdb.dbo.sp_add_jobschedule @job_id=@jobId, @name=N'OccursEvery15Minutes', @enabled=1, @freq_type=4, @freq_interval=1, @freq_subday_type=4, @freq_subday_interval=15, @freq_relative_interval=0, @freq_recurrence_factor=0, @schedule_uid=N'7c3e972b-6e4b-4c61-9061-715d8b9ba531' IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback EXEC @ReturnCode = msdb.dbo.sp_add_jobserver @job_id = @jobId, @server_name = N'(local)' IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback ----------------------------------------------------------------------- -- Remove the sysutility_get_cache_tables_data_into_aggregate_tables_hourly job if it is already existing. ----------------------------------------------------------------------- DECLARE @job_id1 UNIQUEIDENTIFIER SELECT @job_id1 = job_id FROM msdb.dbo.sysjobs_view WHERE name = N'sysutility_get_cache_tables_data_into_aggregate_tables_hourly' IF EXISTS (SELECT job_id FROM msdb.dbo.sysjobs_view WHERE name = N'sysutility_get_cache_tables_data_into_aggregate_tables_hourly') EXEC msdb.dbo.sp_delete_job @job_id=@job_id1, @delete_unused_schedule=1 ----------------------------------------------------------------------- -- sysutility_get_cache_tables_data_into_aggregate_tables_hourly job has following steps: -- 1. Aggregate current days data from [sysutility_ucp_core].[computer_cpu_utilizations_internal] -- and put it into [sysutility_ucp_core].[aggregated_computer_cpu_utilizations_internal]. -- 2. Aggregate current days data from [sysutility_ucp_core].[dac_execution_statistics_internal] -- and put it into [sysutility_ucp_core].[aggregated_dac_cpu_utilizations_internal]. -- 3. Aggregate current days data storage cache table into -- storage aggregation table ----------------------------------------------------------------------- IF NOT EXISTS (SELECT name FROM msdb.dbo.syscategories WHERE name=N'[Uncategorized (Local)]' AND category_class=1) BEGIN EXEC @ReturnCode = msdb.dbo.sp_add_category @class=N'JOB', @type=N'LOCAL', @name=N'[Uncategorized (Local)]' IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback END DECLARE @jobId1 BINARY(16) EXEC @ReturnCode = msdb.dbo.sp_add_job @job_name=N'sysutility_get_cache_tables_data_into_aggregate_tables_hourly', @enabled=1, @notify_level_eventlog=0, @notify_level_email=0, @notify_level_netsend=0, @notify_level_page=0, @delete_level=0, @description=N'At every hour''s stroke, the data of the cache tables get aggregated and put into corresponding aggregate by hour tables.', @category_name=N'[Uncategorized (Local)]', @owner_login_name=@CurrentLoggedIn, @job_id = @jobId1 OUTPUT IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id=@jobId1, @step_name=N'Aggregate current hours data from the cache tables into the server aggregation table', @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'DECLARE @now DATETIMEOFFSET(7); SELECT @now = SYSDATETIMEOFFSET(); EXEC [sysutility_ucp_core].sp_copy_cache_table_data_into_aggregate_tables @aggregation_type=1, @endTime=@now', @database_name = @mdwDBName, @flags=0 IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback EXEC @ReturnCode = msdb.dbo.sp_add_jobschedule @job_id=@jobId1, @name=N'OccursEveryOneHour', @enabled=1, @freq_type=4, @freq_interval=1, @freq_subday_type=8, @freq_subday_interval=1, @freq_relative_interval=0, @freq_recurrence_factor=0, @active_start_time=100 IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback ----------------------------------------------------------------------- -- Remove the sysutility_get_cache_tables_data_into_aggregate_tables_daily job if it is already existing. ----------------------------------------------------------------------- SELECT @job_id1 = job_id FROM msdb.dbo.sysjobs_view WHERE name = N'sysutility_get_cache_tables_data_into_aggregate_tables_daily' IF EXISTS (SELECT job_id FROM msdb.dbo.sysjobs_view WHERE name = N'sysutility_get_cache_tables_data_into_aggregate_tables_daily') EXEC msdb.dbo.sp_delete_job @job_id=@job_id1, @delete_unused_schedule=1 ----------------------------------------------------------------------- -- sysutility_get_cache_tables_data_into_aggregate_tables_daily job has following steps: -- 1. Aggregate current days data from [sysutility_ucp_core].[computer_cpu_utilizations_internal] -- and put it into [sysutility_ucp_core].[aggregated_computer_cpu_utilizations_internal]. -- 2. Aggregate current days data from [sysutility_ucp_core].[dac_execution_statistics_internal] -- and put it into [sysutility_ucp_core].[aggregated_dac_cpu_utilizations_internal]. -- 3. Aggregate current days data from storage cache table into -- storage aggregation table ----------------------------------------------------------------------- IF NOT EXISTS (SELECT name FROM msdb.dbo.syscategories WHERE name=N'[Uncategorized (Local)]' AND category_class=1) BEGIN EXEC @ReturnCode = msdb.dbo.sp_add_category @class=N'JOB', @type=N'LOCAL', @name=N'[Uncategorized (Local)]' IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback END DECLARE @jobId2 BINARY(16) EXEC @ReturnCode = msdb.dbo.sp_add_job @job_name=N'sysutility_get_cache_tables_data_into_aggregate_tables_daily', @enabled=1, @notify_level_eventlog=0, @notify_level_email=0, @notify_level_netsend=0, @notify_level_page=0, @delete_level=0, @description=N'At every 12:01 AM stroke, the data of the cache tables get aggregated and put into corresponding aggregate by day tables.', @category_name=N'[Uncategorized (Local)]', @owner_login_name=@CurrentLoggedIn, @job_id = @jobId2 OUTPUT IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id=@jobId2, @step_name=N'Aggregate current days data from the cache tables into the server aggregation table', @step_id=1, @cmdexec_success_code=0, @on_success_action=3, @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'DECLARE @now DATETIME; SELECT @now = GETUTCDATE(); EXEC [sysutility_ucp_core].sp_copy_cache_table_data_into_aggregate_tables @aggregation_type=2, @endTime=@now', @database_name = @mdwDBName, @flags=0 IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id=@jobId2, @step_name=N'Purge cache table history data based on retention period', @step_id=2, @cmdexec_success_code=0, @on_success_action=3, @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 [sysutility_ucp_core].[sp_purge_cache_tables];', @database_name = @mdwDBName, @flags=0 IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id=@jobId2, @step_name=N'Purge resource health policy evaluation history based on trailing window', @step_id=3, @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 msdb.dbo.sp_sysutility_ucp_delete_policy_history', @database_name=N'msdb', @flags=0 IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback EXEC @ReturnCode = msdb.dbo.sp_update_job @job_id = @jobId1, @start_step_id = 1 IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback EXEC @ReturnCode = msdb.dbo.sp_add_jobschedule @job_id=@jobId2, @name=N'OccursOnceADayAt12:01AM', @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_date=20080218, @active_end_date=99991231, @active_start_time=100, @active_end_time=235959, @schedule_uid=N'acb4d2d5-d2ee-4d33-b82e-a296a41fc225' IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback EXEC @ReturnCode = msdb.dbo.sp_add_jobserver @job_id = @jobId1, @server_name = N'(local)' IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback EXEC @ReturnCode = msdb.dbo.sp_add_jobserver @job_id = @jobId2, @server_name = N'(local)' IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback -- Commit our transaction COMMIT TRANSACTION GOTO EndSave QuitWithRollback: IF (@@TRANCOUNT > 0) ROLLBACK TRANSACTION EndSave: GO -- Commit our transaction COMMIT TRANSACTION GO /**********************************************************************/ /* Setup Utility object permissions */ /**********************************************************************/ /********************************************************************** Create the UtilityMDWWriter role. This role is granted to proxy accounts that run the DC upload step on each MI. It allows these accounts to insert data into the Utility "live" tables in MDW. ***********************************************************************/ RAISERROR ('Create UtilityMDWWriter role...', 0, 1) WITH NOWAIT; IF ( NOT EXISTS (SELECT * FROM sys.database_principals WHERE name = N'UtilityMDWWriter' AND type = 'R')) BEGIN CREATE ROLE [UtilityMDWWriter] 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'UtilityMDWWriter' AND type = 'R') BEGIN -- if the role has no members drop and recreate it DROP ROLE [UtilityMDWWriter] CREATE ROLE [UtilityMDWWriter] END END GO -- UtilityMDWWriter is a member of the mdw_writer role; this will give it -- INSERT, EXEC, and SELECT permissions on anything in the [snapshots] schema. EXECUTE sp_addrolemember @rolename = 'mdw_writer' , @membername = 'UtilityMDWWriter' GO /********************************************************************** Create the UtilityMDWCacheReader role. This sysutility_mdw role corresponds to the UtilityCMRReader role in msdb. A "CMRReader" can select from objects in msdb that expose data in cache tables in sysutility_mdw. The "CacheReader" role in sysutility_mdw secures these cache tables/functions in the MDW db. A login should be a member of both roles in order to run the UCP data processing job. ***********************************************************************/ RAISERROR ('Create UtilityMDWCacheReader role...', 0, 1) WITH NOWAIT; IF ( NOT EXISTS (SELECT * FROM sys.database_principals WHERE name = N'UtilityMDWCacheReader' AND type = 'R')) BEGIN CREATE ROLE [UtilityMDWCacheReader]; 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'UtilityMDWCacheReader' AND type = 'R') BEGIN -- if the role has no members drop and recreate it DROP ROLE [UtilityMDWCacheReader]; CREATE ROLE [UtilityMDWCacheReader]; END; END; GO -- Grant SELECT or EXECUTE on the cache functions/views at the boundary of msdb and -- sysutility_mdw. These objects are directly referenced by objects in msdb. RAISERROR ('Granting permission on MDW cache objects to UtilityMDWCacheReader role', 0, 1) WITH NOWAIT; -- Dimensions GRANT SELECT ON [sysutility_ucp_core].[latest_dacs] TO [UtilityMDWCacheReader]; GRANT SELECT ON [sysutility_ucp_core].[latest_computers] TO [UtilityMDWCacheReader]; GRANT SELECT ON [sysutility_ucp_core].[latest_volumes] TO [UtilityMDWCacheReader]; GRANT SELECT ON [sysutility_ucp_core].[latest_smo_servers] TO [UtilityMDWCacheReader]; GRANT SELECT ON [sysutility_ucp_core].[latest_databases] TO [UtilityMDWCacheReader]; GRANT SELECT ON [sysutility_ucp_core].[latest_filegroups] TO [UtilityMDWCacheReader]; GRANT SELECT ON [sysutility_ucp_core].[latest_datafiles] TO [UtilityMDWCacheReader]; GRANT SELECT ON [sysutility_ucp_core].[latest_logfiles] TO [UtilityMDWCacheReader]; -- Measures GRANT SELECT ON [sysutility_ucp_core].[cpu_utilization] TO [UtilityMDWCacheReader]; GRANT SELECT ON [sysutility_ucp_core].[space_utilization] TO [UtilityMDWCacheReader]; GO -- Put the database back into multi user mode RAISERROR('', 0, 1) WITH NOWAIT; RAISERROR('Restoring database to multi user mode', 0, 1) WITH NOWAIT; 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 RAISERROR('', 0, 1) WITH NOWAIT; RAISERROR('Re-enabling asynchronous auto statistics ...', 0, 1) WITH NOWAIT; 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 RAISERROR('', 0, 1) WITH NOWAIT; RAISERROR('----------------------------------', 0, 1) WITH NOWAIT; RAISERROR('Execution of INSTMDW.SQL complete', 0, 1) WITH NOWAIT; RAISERROR('----------------------------------', 0, 1) WITH NOWAIT; GO