diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 5e502fc3..00000000 Binary files a/.DS_Store and /dev/null differ diff --git a/sp_HealthParser/sp_HealthParser.sql b/sp_HealthParser/sp_HealthParser.sql index 69b772e2..8a3d093d 100644 --- a/sp_HealthParser/sp_HealthParser.sql +++ b/sp_HealthParser/sp_HealthParser.sql @@ -873,13 +873,18 @@ AND ca.utc_timestamp < @end_date'; id bigint IDENTITY, collection_time datetime2(7) NOT NULL DEFAULT SYSDATETIME(), event_time datetime2(7) NULL, - node_id int NULL, - memory_available_gb nvarchar(30) NULL, - memory_requested_gb nvarchar(30) NULL, - memory_allocator nvarchar(256) NULL, - memory_allocation_type nvarchar(256) NULL, - memory_clerk_name nvarchar(256) NULL, - os_error int NULL, + broker_id integer NULL, + pool_metadata_id integer NULL, + delta_time bigint NULL, + memory_ratio integer NULL, + new_target bigint NULL, + overall bigint NULL, + rate bigint NULL, + currently_predicated bigint NULL, + currently_allocated bigint NULL, + previously_allocated bigint NULL, + broker nvarchar(256) NULL, + notification nvarchar(256) NULL, PRIMARY KEY CLUSTERED (collection_time, id) ); IF @debug = 1 BEGIN RAISERROR(''Created table %s for memory broker logging.'', 0, 1, ''' + @log_table_memory_broker + N''') WITH NOWAIT; END; @@ -912,15 +917,33 @@ AND ca.utc_timestamp < @end_date'; id bigint IDENTITY, collection_time datetime2(7) NOT NULL DEFAULT SYSDATETIME(), event_time datetime2(7) NULL, - node_id int NULL, - memory_available_kb nvarchar(30) NULL, - memory_requested_kb nvarchar(30) NULL, - memory_available_mb nvarchar(30) NULL, - memory_requested_mb nvarchar(30) NULL, - memory_allocator nvarchar(256) NULL, - memory_allocation_type nvarchar(256) NULL, - memory_clerk_name nvarchar(256) NULL, - os_error int NULL, + node_id integer NULL, + memory_node_id integer NULL, + memory_utilization_pct integer NULL, + total_physical_memory_kb bigint NULL, + available_physical_memory_kb bigint NULL, + total_page_file_kb bigint NULL, + available_page_file_kb bigint NULL, + total_virtual_address_space_kb bigint NULL, + available_virtual_address_space_kb bigint NULL, + target_kb bigint NULL, + reserved_kb bigint NULL, + committed_kb bigint NULL, + shared_committed_kb bigint NULL, + awe_kb bigint NULL, + pages_kb bigint NULL, + failure_type nvarchar(256) NULL, + failure_value integer NULL, + resources integer NULL, + factor_text nvarchar(256) NULL, + factor_value integer NULL, + last_error integer NULL, + pool_metadata_id integer NULL, + is_process_in_job nvarchar(10) NULL, + is_system_physical_memory_high nvarchar(10) NULL, + is_system_physical_memory_low nvarchar(10) NULL, + is_process_physical_memory_low nvarchar(10) NULL, + is_process_virtual_memory_low nvarchar(10) NULL, PRIMARY KEY CLUSTERED (collection_time, id) ); IF @debug = 1 BEGIN RAISERROR(''Created table %s for memory node OOM logging.'', 0, 1, ''' + @log_table_memory_node_oom + N''') WITH NOWAIT; END; @@ -1040,12 +1063,12 @@ AND ca.utc_timestamp < @end_date'; id bigint IDENTITY, collection_time datetime2(7) NOT NULL DEFAULT SYSDATETIME(), event_time datetime2(7) NULL, - error_number int NULL, - severity int NULL, - state int NULL, + error_number integer NULL, + severity integer NULL, + state integer NULL, message nvarchar(max) NULL, database_name sysname NULL, - database_id int NULL, + database_id integer NULL, PRIMARY KEY CLUSTERED (collection_time, id) ); IF @debug = 1 BEGIN RAISERROR(''Created table %s for severe errors logging.'', 0, 1, ''' + @log_table_severe_errors + N''') WITH NOWAIT; END; @@ -1637,6 +1660,30 @@ AND ca.utc_timestamp < @end_date'; OPTION(RECOMPILE); END; + /* Add memory node OOM collection for MI */ + IF @what_to_check IN ('all', 'memory') + BEGIN + IF @debug = 1 + BEGIN + RAISERROR('Checking Managed Instance memory node OOM events', 0, 0) WITH NOWAIT; + RAISERROR('Inserting #memory_node_oom', 0, 0) WITH NOWAIT; + END; + + INSERT + #memory_node_oom + WITH + (TABLOCKX) + ( + memory_node_oom + ) + SELECT + e.x.query('.') + FROM #ring_buffer AS rb + CROSS APPLY rb.ring_buffer.nodes('/event') AS e(x) + WHERE e.x.exist('@name[.= "memory_node_oom_ring_buffer_recorded"]') = 1 + OPTION(RECOMPILE); + END; + END; /*End Managed Instance collection*/ IF @debug = 1 @@ -3134,19 +3181,23 @@ END; ), w.x.value('@timestamp', 'datetime2') ), - notification_type = w.x.value('(data[@name="notification_type"]/text)[1]', 'nvarchar(256)'), - reclaim_target_kb = w.x.value('(data[@name="reclaim_target_kb"]/value)[1]', 'bigint'), - reclaimed_kb = w.x.value('(data[@name="reclaimed_kb"]/value)[1]', 'bigint'), - pressure = w.x.value('(data[@name="pressure"]/value)[1]', 'bigint'), - currently_available_kb = w.x.value('(data[@name="currently_available_kb"]/value)[1]', 'bigint'), - reserved_kb = w.x.value('(data[@name="reserved_kb"]/value)[1]', 'bigint'), - committed_kb = w.x.value('(data[@name="committed_kb"]/value)[1]', 'bigint'), - worker_count = w.x.value('(data[@name="worker_count"]/value)[1]', 'integer'), + broker_id = w.x.value('(data[@name="id"]/value)[1]', 'integer'), + pool_metadata_id = w.x.value('(data[@name="pool_metadata_id"]/value)[1]', 'integer'), + delta_time = w.x.value('(data[@name="delta_time"]/value)[1]', 'bigint'), + memory_ratio = w.x.value('(data[@name="memory_ratio"]/value)[1]', 'integer'), + new_target = w.x.value('(data[@name="new_target"]/value)[1]', 'bigint'), + overall = w.x.value('(data[@name="overall"]/value)[1]', 'bigint'), + rate = w.x.value('(data[@name="rate"]/value)[1]', 'bigint'), + currently_predicated = w.x.value('(data[@name="currently_predicated"]/value)[1]', 'bigint'), + currently_allocated = w.x.value('(data[@name="currently_allocated"]/value)[1]', 'bigint'), + previously_allocated = w.x.value('(data[@name="previously_allocated"]/value)[1]', 'bigint'), + broker = w.x.value('(data[@name="broker"]/value)[1]', 'nvarchar(256)'), + notification = w.x.value('(data[@name="notification"]/value)[1]', 'nvarchar(256)'), xml = w.x.query('.') INTO #memory_broker_info FROM #memory_broker AS mb CROSS APPLY mb.memory_broker.nodes('//event') AS w(x) - WHERE (w.x.exist('(data[@name="notification_type"]/text[.= "RESOURCE_MEMPHYSICAL_LOW"])') = @warnings_only OR @warnings_only = 0) + WHERE (w.x.exist('(data[@name="notification"]/value[.= "RESOURCE_MEMPHYSICAL_LOW"])') = @warnings_only OR @warnings_only = 0) OPTION(RECOMPILE); IF @debug = 1 @@ -3192,18 +3243,31 @@ END; END; ELSE BEGIN - /* Build the query for memory node OOM events */ + /* Build the query for memory broker notifications */ SET @dsql = N' SELECT ' + CASE WHEN @log_to_table = 1 - THEN N'' - ELSE N'finding = ''memory node OOM events'',' - END + - N' - mnoi.event_time, - mnoi.node_id, - memory_available_gb = + THEN N'mbi.event_time, + mbi.broker_id, + mbi.pool_metadata_id, + mbi.delta_time, + mbi.memory_ratio, + mbi.new_target, + mbi.overall, + mbi.rate, + mbi.currently_predicated, + mbi.currently_allocated, + mbi.previously_allocated, + mbi.broker, + mbi.notification' + ELSE N'finding = ''memory broker notifications'', + mbi.event_time, + mbi.broker_id, + mbi.pool_metadata_id, + mbi.delta_time, + mbi.memory_ratio, + new_target_gb = REPLACE ( CONVERT @@ -3212,14 +3276,14 @@ END; CONVERT ( money, - mnoi.memory_available_kb / 1024.0 / 1024.0 + mbi.new_target / 1024.0 / 1024.0 ), 1 ), N''.00'', N'''' ), - memory_requested_gb = + overall_gb = REPLACE ( CONVERT @@ -3228,18 +3292,67 @@ END; CONVERT ( money, - mnoi.memory_requested_kb / 1024.0 / 1024.0 + mbi.overall / 1024.0 / 1024.0 ), 1 ), N''.00'', N'''' ), - mnoi.memory_allocator, - mnoi.memory_allocation_type, - mnoi.memory_clerk_name, - mnoi.os_error - FROM #memory_node_oom_info AS mnoi'; + mbi.rate, + currently_predicated_gb = + REPLACE + ( + CONVERT + ( + nvarchar(30), + CONVERT + ( + money, + mbi.currently_predicated / 1024.0 / 1024.0 + ), + 1 + ), + N''.00'', + N'''' + ), + currently_allocated_gb = + REPLACE + ( + CONVERT + ( + nvarchar(30), + CONVERT + ( + money, + mbi.currently_allocated / 1024.0 / 1024.0 + ), + 1 + ), + N''.00'', + N'''' + ), + previously_allocated_gb = + REPLACE + ( + CONVERT + ( + nvarchar(30), + CONVERT + ( + money, + mbi.previously_allocated / 1024.0 / 1024.0 + ), + 1 + ), + N''.00'', + N'''' + ), + mbi.broker, + mbi.notification' + END + + N' + FROM #memory_broker_info AS mbi'; /* Add the WHERE clause only for table logging */ IF @log_to_table = 1 @@ -3287,13 +3400,18 @@ END; + @log_table_memory_broker + N' ( event_time, - node_id, - memory_available_gb, - memory_requested_gb, - memory_allocator, - memory_allocation_type, - memory_clerk_name, - os_error + broker_id, + pool_metadata_id, + delta_time, + memory_ratio, + new_target, + overall, + rate, + currently_predicated, + currently_allocated, + previously_allocated, + broker, + notification )' + @dsql; @@ -3343,13 +3461,33 @@ END; ), w.x.value('@timestamp', 'datetime2') ), - node_id = w.x.value('(data[@name="id"]/value)[1]', 'int'), - memory_available_kb = w.x.value('(data[@name="availableMemory"]/value)[1]', 'bigint'), - memory_requested_kb = w.x.value('(data[@name="requestedMemory"]/value)[1]', 'bigint'), - memory_allocator = w.x.value('(data[@name="allocator"]/text)[1]', 'nvarchar(256)'), - memory_allocation_type = w.x.value('(data[@name="allocationType"]/text)[1]', 'nvarchar(256)'), - memory_clerk_name = w.x.value('(data[@name="memoryClerk"]/text)[1]', 'nvarchar(256)'), - os_error = w.x.value('(data[@name="oserror"]/value)[1]', 'integer'), + node_id = w.x.value('(data[@name="id"]/value)[1]', 'integer'), + memory_node_id = w.x.value('(data[@name="memory_node_id"]/value)[1]', 'integer'), + memory_utilization_pct = w.x.value('(data[@name="memory_utilization_pct"]/value)[1]', 'integer'), + total_physical_memory_kb = w.x.value('(data[@name="total_physical_memory_kb"]/value)[1]', 'bigint'), + available_physical_memory_kb = w.x.value('(data[@name="available_physical_memory_kb"]/value)[1]', 'bigint'), + total_page_file_kb = w.x.value('(data[@name="total_page_file_kb"]/value)[1]', 'bigint'), + available_page_file_kb = w.x.value('(data[@name="available_page_file_kb"]/value)[1]', 'bigint'), + total_virtual_address_space_kb = w.x.value('(data[@name="total_virtual_address_space_kb"]/value)[1]', 'bigint'), + available_virtual_address_space_kb = w.x.value('(data[@name="available_virtual_address_space_kb"]/value)[1]', 'bigint'), + target_kb = w.x.value('(data[@name="target_kb"]/value)[1]', 'bigint'), + reserved_kb = w.x.value('(data[@name="reserved_kb"]/value)[1]', 'bigint'), + committed_kb = w.x.value('(data[@name="committed_kb"]/value)[1]', 'bigint'), + shared_committed_kb = w.x.value('(data[@name="shared_committed_kb"]/value)[1]', 'bigint'), + awe_kb = w.x.value('(data[@name="awe_kb"]/value)[1]', 'bigint'), + pages_kb = w.x.value('(data[@name="pages_kb"]/value)[1]', 'bigint'), + failure_type = w.x.value('(data[@name="failure"]/text)[1]', 'nvarchar(256)'), + failure_value = w.x.value('(data[@name="failure"]/value)[1]', 'integer'), + resources = w.x.value('(data[@name="resources"]/value)[1]', 'integer'), + factor_text = w.x.value('(data[@name="factor"]/text)[1]', 'nvarchar(256)'), + factor_value = w.x.value('(data[@name="factor"]/value)[1]', 'integer'), + last_error = w.x.value('(data[@name="last_error"]/value)[1]', 'integer'), + pool_metadata_id = w.x.value('(data[@name="pool_metadata_id"]/value)[1]', 'integer'), + is_process_in_job = w.x.value('(data[@name="is_process_in_job"]/value)[1]', 'nvarchar(10)'), + is_system_physical_memory_high = w.x.value('(data[@name="is_system_physical_memory_high"]/value)[1]', 'nvarchar(10)'), + is_system_physical_memory_low = w.x.value('(data[@name="is_system_physical_memory_low"]/value)[1]', 'nvarchar(10)'), + is_process_physical_memory_low = w.x.value('(data[@name="is_process_physical_memory_low"]/value)[1]', 'nvarchar(10)'), + is_process_virtual_memory_low = w.x.value('(data[@name="is_process_virtual_memory_low"]/value)[1]', 'nvarchar(10)'), xml = w.x.query('.') INTO #memory_node_oom_info FROM #memory_node_oom AS mno @@ -3397,18 +3535,45 @@ END; END; ELSE BEGIN - /* Build the query for memory broker notifications */ + /* Build the query for memory node OOM events */ SET @dsql = N' SELECT ' + CASE WHEN @log_to_table = 1 - THEN N'' - ELSE N'finding = ''memory broker notifications'',' - END + - N' - mbi.event_time, - mbi.notification_type, - reclaim_target_gb = + THEN N'mnoi.event_time, + mnoi.node_id, + mnoi.memory_node_id, + mnoi.memory_utilization_pct, + mnoi.total_physical_memory_kb, + mnoi.available_physical_memory_kb, + mnoi.total_page_file_kb, + mnoi.available_page_file_kb, + mnoi.total_virtual_address_space_kb, + mnoi.available_virtual_address_space_kb, + mnoi.target_kb, + mnoi.reserved_kb, + mnoi.committed_kb, + mnoi.shared_committed_kb, + mnoi.awe_kb, + mnoi.pages_kb, + mnoi.failure_type, + mnoi.failure_value, + mnoi.resources, + mnoi.factor_text, + mnoi.factor_value, + mnoi.last_error, + mnoi.pool_metadata_id, + mnoi.is_process_in_job, + mnoi.is_system_physical_memory_high, + mnoi.is_system_physical_memory_low, + mnoi.is_process_physical_memory_low, + mnoi.is_process_virtual_memory_low' + ELSE N'finding = ''memory node OOM events'', + mnoi.event_time, + mnoi.node_id, + mnoi.memory_node_id, + mnoi.memory_utilization_pct, + total_physical_memory_gb = REPLACE ( CONVERT @@ -3417,14 +3582,14 @@ END; CONVERT ( money, - mbi.reclaim_target_kb / 1024.0 / 1024.0 + mnoi.total_physical_memory_kb / 1024.0 / 1024.0 ), 1 ), N''.00'', N'''' ), - reclaimed_gb = + available_physical_memory_gb = REPLACE ( CONVERT @@ -3433,20 +3598,30 @@ END; CONVERT ( money, - mbi.reclaimed_kb / 1024.0 / 1024.0 + mnoi.available_physical_memory_kb / 1024.0 / 1024.0 ), 1 ), N''.00'', N'''' ), - reclaim_success_percent = - CASE - WHEN mbi.reclaim_target_kb > 0 - THEN CONVERT(DECIMAL(5,2), 100.0 * mbi.reclaimed_kb / mbi.reclaim_target_kb) - ELSE 0 - END, - pressure_gb = + total_page_file_gb = + REPLACE + ( + CONVERT + ( + nvarchar(30), + CONVERT + ( + money, + mnoi.total_page_file_kb / 1024.0 / 1024.0 + ), + 1 + ), + N''.00'', + N'''' + ), + available_page_file_gb = REPLACE ( CONVERT @@ -3455,14 +3630,14 @@ END; CONVERT ( money, - mbi.pressure_mb / 1024.0 + mnoi.available_page_file_kb / 1024.0 / 1024.0 ), 1 ), N''.00'', N'''' ), - currently_available_gb = + target_gb = REPLACE ( CONVERT @@ -3471,7 +3646,7 @@ END; CONVERT ( money, - mbi.currently_available_kb / 1024.0 / 1024.0 + mnoi.target_kb / 1024.0 / 1024.0 ), 1 ), @@ -3487,7 +3662,7 @@ END; CONVERT ( money, - mbi.reserved_kb / 1024.0 / 1024.0 + mnoi.reserved_kb / 1024.0 / 1024.0 ), 1 ), @@ -3503,15 +3678,76 @@ END; CONVERT ( money, - mbi.committed_kb / 1024.0 / 1024.0 + mnoi.committed_kb / 1024.0 / 1024.0 ), 1 ), N''.00'', N'''' ), - mbi.worker_count - FROM #memory_broker_info AS mbi'; + shared_committed_gb = + REPLACE + ( + CONVERT + ( + nvarchar(30), + CONVERT + ( + money, + mnoi.shared_committed_kb / 1024.0 / 1024.0 + ), + 1 + ), + N''.00'', + N'''' + ), + awe_gb = + REPLACE + ( + CONVERT + ( + nvarchar(30), + CONVERT + ( + money, + mnoi.awe_kb / 1024.0 / 1024.0 + ), + 1 + ), + N''.00'', + N'''' + ), + pages_gb = + REPLACE + ( + CONVERT + ( + nvarchar(30), + CONVERT + ( + money, + mnoi.pages_kb / 1024.0 / 1024.0 + ), + 1 + ), + N''.00'', + N'''' + ), + mnoi.failure_type, + mnoi.failure_value, + mnoi.resources, + mnoi.factor_text, + mnoi.factor_value, + mnoi.last_error, + mnoi.pool_metadata_id, + mnoi.is_process_in_job, + mnoi.is_system_physical_memory_high, + mnoi.is_system_physical_memory_low, + mnoi.is_process_physical_memory_low, + mnoi.is_process_virtual_memory_low' + END + + N' + FROM #memory_node_oom_info AS mnoi'; /* Add the WHERE clause only for table logging */ IF @log_to_table = 1 @@ -3559,15 +3795,33 @@ END; ' + @log_table_memory_node_oom + N' ( event_time, - notification_type, - reclaim_target_gb, - reclaimed_gb, - reclaim_success_percent, - pressure_gb, - currently_available_gb, - reserved_gb, - committed_gb, - worker_count + node_id, + memory_node_id, + memory_utilization_pct, + total_physical_memory_kb, + available_physical_memory_kb, + total_page_file_kb, + available_page_file_kb, + total_virtual_address_space_kb, + available_virtual_address_space_kb, + target_kb, + reserved_kb, + committed_kb, + shared_committed_kb, + awe_kb, + pages_kb, + failure_type, + failure_value, + resources, + factor_text, + factor_value, + last_error, + pool_metadata_id, + is_process_in_job, + is_system_physical_memory_high, + is_system_physical_memory_low, + is_process_physical_memory_low, + is_process_virtual_memory_low )' + @dsql; @@ -4366,7 +4620,7 @@ END; isolation_level = bd.value('(process/@isolationlevel)[1]', 'nvarchar(50)'), log_used = bd.value('(process/@logused)[1]', 'bigint'), clientoption1 = bd.value('(process/@clientoption1)[1]', 'bigint'), - clientoption2 = bd.value('(process/@clientoption1)[1]', 'bigint'), + clientoption2 = bd.value('(process/@clientoption2)[1]', 'bigint'), activity = CASE WHEN bd.exist('//blocked-process-report/blocked-process') = 1 THEN 'blocked' END, blocked_process_report = bd.query('.') INTO #blocked @@ -4427,7 +4681,7 @@ END; isolation_level = bg.value('(process/@isolationlevel)[1]', 'nvarchar(50)'), log_used = bg.value('(process/@logused)[1]', 'bigint'), clientoption1 = bg.value('(process/@clientoption1)[1]', 'bigint'), - clientoption2 = bg.value('(process/@clientoption1)[1]', 'bigint'), + clientoption2 = bg.value('(process/@clientoption2)[1]', 'bigint'), activity = CASE WHEN bg.exist('//blocked-process-report/blocking-process') = 1 THEN 'blocking' END, blocked_process_report = bg.query('.') INTO #blocking diff --git a/sp_HumanEvents/README.md b/sp_HumanEvents/README.md index e28150c4..0e916b88 100644 --- a/sp_HumanEvents/README.md +++ b/sp_HumanEvents/README.md @@ -122,7 +122,9 @@ EXECUTE dbo.sp_HumanEvents This was originally a companion script to analyze the blocked process report Extended Event created by sp_HumanEvents, but has since turned into its own monster. -It will work on any Extended Event that captures the blocked process report. If you need to set that up, run these two pieces of code. +It will work on any Extended Event that captures the blocked process report. If you need to set that up, run the next two pieces of code. + +The system_health Extended Event works, but its blocked process report is much less comprehensive than the real thing. I do not allow logging to a table from this, because the set of columns and available data is too incomplete, and I don't want to juggle multiple table definitions. ## Setup @@ -172,28 +174,29 @@ ON SERVER ## Parameters -| parameter_name | data_type | description | valid_inputs | defaults | -|-----------------------|-----------|-------------------------------------------------|------------------------------------------------------------------------|------------------------------------| -| @session_name | sysname | name of the extended event session to pull from | extended event session name capturing sqlserver.blocked_process_report | keeper_HumanEvents_blocking | -| @target_type | sysname | target of the extended event session | event_file or ring_buffer | NULL | -| @start_date | datetime2 | filter by date | a reasonable date | NULL; will shortcut to last 7 days | -| @end_date | datetime2 | filter by date | a reasonable date | NULL | -| @database_name | sysname | filter by database name | a database that exists on this server | NULL | -| @object_name | sysname | filter by table name | a schema-prefixed table name | NULL | -| @target_database | sysname | database containing the table with BPR data | a valid database name | NULL | -| @target_schema | sysname | schema of the table | a valid schema name | NULL | -| @target_table | sysname | table name | a valid table name | NULL | -| @target_column | sysname | column containing XML data | a valid column name | NULL | -| @timestamp_column | sysname | column containing timestamp (optional) | a valid column name | NULL | -| @log_to_table | bit | enable logging to permanent tables | 0 or 1 | 0 | -| @log_database_name | sysname | database to store logging tables | a valid database name | NULL | -| @log_schema_name | sysname | schema to store logging tables | a valid schema name | NULL | -| @log_table_name_prefix| sysname | prefix for all logging tables | a valid table name prefix | 'HumanEventsBlockViewer' | -| @log_retention_days | integer | Number of days to keep logs, 0 = keep indefinitely | a valid integer | 30 | -| @help | bit | how you got here | 0 or 1 | 0 | -| @debug | bit | dumps raw temp table contents | 0 or 1 | 0 | -| @version | varchar | OUTPUT; for support | none; OUTPUT | none; OUTPUT | -| @version_date | datetime | OUTPUT; for support | none; OUTPUT | none; OUTPUT | +| parameter_name | data_type | description | valid_inputs | defaults | +|-----------------------|-----------|----------------------------------------------------|--------------------------------------------------------------------------------------------------|------------------------------------| +| @session_name | sysname | name of the extended event session to pull from | extended event session name capturing sqlserver.blocked_process_report, system_health also works | keeper_HumanEvents_blocking | +| @target_type | sysname | target of the extended event session | event_file or ring_buffer or table | NULL | +| @start_date | datetime2 | filter by date | a reasonable date | NULL; will shortcut to last 7 days | +| @end_date | datetime2 | filter by date | a reasonable date | NULL | +| @database_name | sysname | filter by database name | a database that exists on this server | NULL | +| @object_name | sysname | filter by table name | a schema-prefixed table name | NULL | +| @target_database | sysname | database containing the table with BPR data | a valid database name | NULL | +| @target_schema | sysname | schema of the table | a valid schema name | NULL | +| @target_table | sysname | table name | a valid table name | NULL | +| @target_column | sysname | column containing XML data | a valid column name | NULL | +| @timestamp_column | sysname | column containing timestamp (optional) | a valid column name | NULL | +| @log_to_table | bit | enable logging to permanent tables | 0 or 1 | 0 | +| @log_database_name | sysname | database to store logging tables | a valid database name | NULL | +| @log_schema_name | sysname | schema to store logging tables | a valid schema name | NULL | +| @log_table_name_prefix| sysname | prefix for all logging tables | a valid table name prefix | 'HumanEventsBlockViewer' | +| @log_retention_days | integer | Number of days to keep logs, 0 = keep indefinitely | a valid integer | 30 | +| @max_blocking_events | bigint | maximum blocking events to analyze, 0 = unlimited | 0 to 9223372036854775807 | 5000 | +| @help | bit | how you got here | 0 or 1 | 0 | +| @debug | bit | dumps raw temp table contents | 0 or 1 | 0 | +| @version | varchar | OUTPUT; for support | none; OUTPUT | none; OUTPUT | +| @version_date | datetime | OUTPUT; for support | none; OUTPUT | none; OUTPUT | ## Usage Examples @@ -219,4 +222,4 @@ EXECUTE dbo.sp_HumanEventsBlockViewer @log_to_table = 1, @log_database_name = 'DBA', @log_schema_name = 'dbo'; -``` \ No newline at end of file +``` diff --git a/sp_HumanEvents/sp_HumanEventsBlockViewer.sql b/sp_HumanEvents/sp_HumanEventsBlockViewer.sql index a8f55673..6b4f985a 100644 --- a/sp_HumanEvents/sp_HumanEventsBlockViewer.sql +++ b/sp_HumanEvents/sp_HumanEventsBlockViewer.sql @@ -78,6 +78,7 @@ ALTER PROCEDURE @log_schema_name sysname = NULL, /*schema to store logging tables*/ @log_table_name_prefix sysname = 'HumanEventsBlockViewer', /*prefix for all logging tables*/ @log_retention_days integer = 30, /*Number of days to keep logs, 0 = keep indefinitely*/ + @max_blocking_events bigint = 5000, /*maximum blocking events to analyze, 0 = unlimited*/ @help bit = 0, /*get help with this procedure*/ @debug bit = 0, /*print dynamic sql and select temp table contents*/ @version varchar(30) = NULL OUTPUT, /*check the version number*/ @@ -105,6 +106,7 @@ BEGIN SELECT 'it will also work with any other extended event session that captures blocking' UNION ALL SELECT 'just use the @session_name parameter to point me there' UNION ALL SELECT 'EXECUTE dbo.sp_HumanEventsBlockViewer @session_name = N''blocked_process_report'';' UNION ALL + SELECT 'the system_health session also works, if you are okay with its lousy blocked process report' SELECT 'all scripts and documentation are available here: https://code.erikdarling.com' UNION ALL SELECT 'from your loving sql server consultant, erik darling: https://erikdarling.com'; @@ -130,6 +132,7 @@ BEGIN WHEN N'@log_schema_name' THEN N'schema to store logging tables' WHEN N'@log_table_name_prefix' THEN N'prefix for all logging tables' WHEN N'@log_retention_days' THEN N'how many days of data to retain' + WHEN N'@max_blocking_events' THEN N'maximum blocking events to analyze, 0 = unlimited' WHEN N'@help' THEN 'how you got here' WHEN N'@debug' THEN 'dumps raw temp table contents' WHEN N'@version' THEN 'OUTPUT; for support' @@ -137,8 +140,8 @@ BEGIN END, valid_inputs = CASE ap.name - WHEN N'@session_name' THEN 'extended event session name capturing sqlserver.blocked_process_report' - WHEN N'@target_type' THEN 'event_file or ring_buffer' + WHEN N'@session_name' THEN 'extended event session name capturing sqlserver.blocked_process_report, system_health also works' + WHEN N'@target_type' THEN 'event_file or ring_buffer or table' WHEN N'@start_date' THEN 'a reasonable date' WHEN N'@end_date' THEN 'a reasonable date' WHEN N'@database_name' THEN 'a database that exists on this server' @@ -153,6 +156,7 @@ BEGIN WHEN N'@log_schema_name' THEN N'any valid schema name' WHEN N'@log_table_name_prefix' THEN N'any valid identifier' WHEN N'@log_retention_days' THEN N'a positive integer' + WHEN N'@max_blocking_events' THEN N'0 to 9223372036854775807 (0 = unlimited)' WHEN N'@help' THEN '0 or 1' WHEN N'@debug' THEN '0 or 1' WHEN N'@version' THEN 'none; OUTPUT' @@ -176,6 +180,7 @@ BEGIN WHEN N'@log_schema_name' THEN N'NULL (dbo)' WHEN N'@log_table_name_prefix' THEN N'HumanEventsBlockViewer' WHEN N'@log_retention_days' THEN N'30' + WHEN N'@max_blocking_events' THEN N'5000' WHEN N'@help' THEN '0' WHEN N'@debug' THEN '0' WHEN N'@version' THEN 'none; OUTPUT' @@ -195,7 +200,7 @@ BEGIN N'check the messages tab for setup commands'; RAISERROR(' -The blocked process report needs to be enabled: +Unless you want to use the lousy version in system_health, the blocked process report needs to be enabled: EXECUTE sys.sp_configure ''show advanced options'', 1; EXECUTE sys.sp_configure ''blocked process threshold'', 5; /* Seconds of blocking before a report is generated */ RECONFIGURE;', 0, 1) WITH NOWAIT; @@ -271,10 +276,11 @@ IF EXISTS 1/0 FROM sys.configurations AS c WHERE c.name = N'blocked process threshold (s)' - AND CONVERT(int, c.value_in_use) = 0 + AND CONVERT(integer, c.value_in_use) = 0 ) +AND @session_name NOT LIKE N'system%health' BEGIN - RAISERROR(N'The blocked process report needs to be enabled: + RAISERROR(N'Unless you want to use the lousy version in system_health, the blocked process report needs to be enabled: EXECUTE sys.sp_configure ''show advanced options'', 1; EXECUTE sys.sp_configure ''blocked process threshold'', 5; /* Seconds of blocking before a report is generated */ RECONFIGURE;', @@ -289,8 +295,9 @@ IF EXISTS 1/0 FROM sys.configurations AS c WHERE c.name = N'blocked process threshold (s)' - AND CONVERT(int, c.value_in_use) <> 5 + AND CONVERT(integer, c.value_in_use) <> 5 ) +AND @session_name NOT LIKE N'system%health' BEGIN RAISERROR(N'For best results, set up the blocked process report like this: EXECUTE sys.sp_configure ''show advanced options'', 1; @@ -336,7 +343,10 @@ DECLARE @log_database_schema nvarchar(1024), @max_event_time datetime2(7), @dsql nvarchar(max) = N'', - @mdsql nvarchar(max) = N''; + @mdsql nvarchar(max) = N'', + @actual_start_date datetime2(7), + @actual_end_date datetime2(7), + @actual_event_count bigint; /*Use some sane defaults for input parameters*/ IF @debug = 1 @@ -439,6 +449,47 @@ BEGIN FROM {table_check}; END;'; +IF @debug = 1 +AND @is_system_health = 0 +BEGIN + RAISERROR('We are not using system_health', 0, 1) WITH NOWAIT; +END; + +IF @is_system_health = 1 +BEGIN + RAISERROR('For best results, consider not using system_health as your target. Re-run with @help = 1 for guidance.', 0, 1) WITH NOWAIT; +END + +/* +Note: I do not allow logging to a table from system_health, because the set of columns +and available data is too incomplete, and I don't want to juggle multiple +table definitions. + +Logging to a table is only allowed from a blocked_process_report Extended Event, +but it can either be ring buffer or file target. I don't care about that. +*/ +IF @is_system_health = 1 +AND +( + LOWER(@target_type) = N'table' + OR @log_to_table = 1 +) +BEGIN + RAISERROR('Logging system_health to a table is not supported. +Either pick a different session or change both +@target_type to be ''event_file'' or ''ring_buffer'' +and @log_to_table to be 0.', 11, 0) WITH NOWAIT; + RETURN; +END + +IF @is_system_health = 1 +AND @target_type IS NULL +BEGIN + RAISERROR('No @target_type specified, using the ''event_file'' for system_health.', 0, 1) WITH NOWAIT; + SELECT + @target_type = 'event_file'; +END + SELECT @azure_msg = CONVERT(nchar(1), @azure), @@ -450,6 +501,7 @@ IF ISNULL(@target_database, DB_NAME()) IS NOT NULL AND ISNULL(@target_schema, N'dbo') IS NOT NULL AND @target_table IS NOT NULL AND @target_column IS NOT NULL +AND @is_system_health = 0 BEGIN SET @target_type = N'table'; END; @@ -726,22 +778,22 @@ BEGIN contentious_object nvarchar(4000) NULL, activity varchar(8) NULL, blocking_tree varchar(8000) NULL, - spid int NULL, - ecid int NULL, + spid integer NULL, + ecid integer NULL, query_text xml NULL, wait_time_ms bigint NULL, status nvarchar(10) NULL, isolation_level nvarchar(50) NULL, lock_mode nvarchar(10) NULL, resource_owner_type nvarchar(256) NULL, - transaction_count int NULL, + transaction_count integer NULL, transaction_name nvarchar(1024) NULL, last_transaction_started datetime2(7) NULL, last_transaction_completed datetime2(7) NULL, client_option_1 varchar(261) NULL, client_option_2 varchar(307) NULL, wait_resource nvarchar(1024) NULL, - priority int NULL, + priority integer NULL, log_used bigint NULL, client_app nvarchar(256) NULL, host_name nvarchar(256) NULL, @@ -1091,11 +1143,19 @@ BEGIN human_events_xml ) SELECT - human_events_xml = e.x.query('.') - FROM #x AS x - CROSS APPLY x.x.nodes('/RingBufferTarget/event') AS e(x) - WHERE e.x.exist('@name[ .= "blocked_process_report"]') = 1 - AND e.x.exist('@timestamp[. >= sql:variable("@start_date") and .< sql:variable("@end_date")]') = 1 + human_events_xml + FROM + ( + SELECT TOP (CASE WHEN @max_blocking_events > 0 THEN @max_blocking_events ELSE 9223372036854775807 END) + human_events_xml = e.x.query('.'), + event_timestamp = e.x.value('@timestamp', 'datetime2') + FROM #x AS x + CROSS APPLY x.x.nodes('/RingBufferTarget/event') AS e(x) + WHERE e.x.exist('@name[ .= "blocked_process_report"]') = 1 + AND e.x.exist('@timestamp[. >= sql:variable("@start_date") and .< sql:variable("@end_date")]') = 1 + ORDER BY + event_timestamp DESC + ) AS most_recent OPTION(RECOMPILE); END; @@ -1115,28 +1175,31 @@ BEGIN human_events_xml ) SELECT - human_events_xml = e.x.query('.') - FROM #x AS x - CROSS APPLY x.x.nodes('/event') AS e(x) - WHERE e.x.exist('@name[ .= "blocked_process_report"]') = 1 - AND e.x.exist('@timestamp[. >= sql:variable("@start_date") and .< sql:variable("@end_date")]') = 1 + human_events_xml + FROM + ( + SELECT TOP (CASE WHEN @max_blocking_events > 0 THEN @max_blocking_events ELSE 9223372036854775807 END) + human_events_xml = e.x.query('.'), + event_timestamp = e.x.value('@timestamp', 'datetime2') + FROM #x AS x + CROSS APPLY x.x.nodes('/event') AS e(x) + WHERE e.x.exist('@name[ .= "blocked_process_report"]') = 1 + AND e.x.exist('@timestamp[. >= sql:variable("@start_date") and .< sql:variable("@end_date")]') = 1 + ORDER BY + event_timestamp DESC + ) AS most_recent OPTION(RECOMPILE); END; /* This section is special for the well-hidden and much less comprehensive blocked -process report stored in the system health extended event session +process report stored in the system health extended event session. -Note: I do not allow logging to a table from this, because the set of columns -and available data is too incomplete, and I don't want to juggle multiple -table definitions. - -Logging to a table is only allowed from the a blocked_process_report Extended Event, -but it can either be ring buffer or file target. I don't care about that. +We disallow many features here. +See where @is_system_health was declared for details. +That is also where we error out if somebody tries to use an unsupported feature. */ IF @is_system_health = 1 -AND LOWER(@target_type) <> N'table' -AND @log_to_table = 0 BEGIN IF @debug = 1 BEGIN @@ -1171,22 +1234,31 @@ BEGIN END; SELECT - event_time = - DATEADD - ( - MINUTE, - DATEDIFF + event_time, + human_events_xml + INTO #blocking_xml_sh + FROM + ( + SELECT TOP (CASE WHEN @max_blocking_events > 0 THEN @max_blocking_events ELSE 9223372036854775807 END) + event_time = + DATEADD ( MINUTE, - GETUTCDATE(), - SYSDATETIME() + DATEDIFF + ( + MINUTE, + GETUTCDATE(), + SYSDATETIME() + ), + w.x.value('(//@timestamp)[1]', 'datetime2') ), - w.x.value('(//@timestamp)[1]', 'datetime2') - ), - human_events_xml = w.x.query('//data[@name="data"]/value/queryProcessing/blockingTasks/blocked-process-report') - INTO #blocking_xml_sh - FROM #sp_server_diagnostics_component_result AS wi - CROSS APPLY wi.sp_server_diagnostics_component_result.nodes('//event') AS w(x) + human_events_xml = w.x.query('//data[@name="data"]/value/queryProcessing/blockingTasks/blocked-process-report'), + event_timestamp = w.x.value('(//@timestamp)[1]', 'datetime2') + FROM #sp_server_diagnostics_component_result AS wi + CROSS APPLY wi.sp_server_diagnostics_component_result.nodes('//event') AS w(x) + ORDER BY + event_timestamp DESC + ) AS most_recent OPTION(RECOMPILE); IF @debug = 1 @@ -1219,7 +1291,7 @@ BEGIN isolation_level = bd.value('(process/@isolationlevel)[1]', 'nvarchar(50)'), log_used = bd.value('(process/@logused)[1]', 'bigint'), clientoption1 = bd.value('(process/@clientoption1)[1]', 'bigint'), - clientoption2 = bd.value('(process/@clientoption1)[1]', 'bigint'), + clientoption2 = bd.value('(process/@clientoption2)[1]', 'bigint'), activity = CASE WHEN bd.exist('//blocked-process-report/blocked-process') = 1 THEN 'blocked' END, blocked_process_report = bd.query('.') INTO #blocked_sh @@ -1276,7 +1348,7 @@ BEGIN isolation_level = bg.value('(process/@isolationlevel)[1]', 'nvarchar(50)'), log_used = bg.value('(process/@logused)[1]', 'bigint'), clientoption1 = bg.value('(process/@clientoption1)[1]', 'bigint'), - clientoption2 = bg.value('(process/@clientoption1)[1]', 'bigint'), + clientoption2 = bg.value('(process/@clientoption2)[1]', 'bigint'), activity = CASE WHEN bg.exist('//blocked-process-report/blocking-process') = 1 THEN 'blocking' END, blocked_process_report = bg.query('.') INTO #blocking_sh @@ -1952,7 +2024,7 @@ SELECT isolation_level = bg.value('(process/@isolationlevel)[1]', 'nvarchar(50)'), log_used = bg.value('(process/@logused)[1]', 'bigint'), clientoption1 = bg.value('(process/@clientoption1)[1]', 'bigint'), - clientoption2 = bg.value('(process/@clientoption1)[1]', 'bigint'), + clientoption2 = bg.value('(process/@clientoption2)[1]', 'bigint'), currentdbname = bg.value('(process/@currentdbname)[1]', 'nvarchar(128)'), currentdbid = bg.value('(process/@currentdb)[1]', 'integer'), blocking_level = 0, @@ -2759,8 +2831,23 @@ BEGIN ap.avg_worker_time_ms DESC OPTION(RECOMPILE, LOOP JOIN, HASH JOIN); + /* Capture actual date range and event count from the data */ + SELECT + @actual_start_date = MIN(event_time), + @actual_end_date = MAX(event_time), + @actual_event_count = COUNT_BIG(DISTINCT monitor_loop) + FROM #blocked + WHERE event_time IS NOT NULL; + + /* Use original dates if no data found */ + SELECT + @actual_start_date = ISNULL(@actual_start_date, @start_date_original), + @actual_end_date = ISNULL(@actual_end_date, @end_date_original), + @actual_event_count = ISNULL(@actual_event_count, 0); + IF @debug = 1 BEGIN + RAISERROR('Actual date range: %s to %s, Event count: %I64d', 0, 1, @actual_start_date, @actual_end_date, @actual_event_count) WITH NOWAIT; RAISERROR('Inserting #block_findings, check_id -1', 0, 1) WITH NOWAIT; END; @@ -2777,9 +2864,27 @@ BEGIN SELECT check_id = -1, database_name = N'erikdarling.com', - object_name = N'sp_HumanEventsBlockViewer version ' + CONVERT(nvarchar(30), @version) + N'.', + object_name = + N'sp_HumanEventsBlockViewer version ' + + CONVERT(nvarchar(30), @version) + + N'.', finding_group = N'https://code.erikdarling.com', - finding = N'blocking for period ' + CONVERT(nvarchar(30), @start_date_original, 126) + N' through ' + CONVERT(nvarchar(30), @end_date_original, 126) + N'.', + finding = + N'blocking events from ' + + CONVERT(nvarchar(30), @actual_start_date, 126) + + N' to ' + + CONVERT(nvarchar(30), @actual_end_date, 126) + + N' (' + CONVERT(nvarchar(30), @actual_event_count) + + N' total events' + + CASE + WHEN @max_blocking_events > 0 + AND @actual_event_count >= @max_blocking_events + THEN N', limited to most recent ' + + CONVERT(nvarchar(30), @max_blocking_events) + + N')' + ELSE N')' + END + + N'.', 1; IF @debug = 1 @@ -3608,7 +3713,9 @@ BEGIN SELECT check_id = 2147483647, database_name = N'erikdarling.com', - object_name = N'sp_HumanEventsBlockViewer version ' + CONVERT(nvarchar(30), @version) + N'.', + object_name = + N'sp_HumanEventsBlockViewer version ' + + CONVERT(nvarchar(30), @version) + N'.', finding_group = N'https://code.erikdarling.com', finding = N'thanks for using me!', 2147483647; diff --git a/sp_IndexCleanup/sp_IndexCleanup.sql b/sp_IndexCleanup/sp_IndexCleanup.sql index 04d25717..21bcc65e 100644 --- a/sp_IndexCleanup/sp_IndexCleanup.sql +++ b/sp_IndexCleanup/sp_IndexCleanup.sql @@ -51,8 +51,8 @@ ALTER PROCEDURE dbo.sp_IndexCleanup ( @database_name sysname = NULL, /*focus on a single database*/ - @schema_name sysname = NULL, /*use when focusing on a single table, or to a single schema with no table name*/ - @table_name sysname = NULL, /*use when focusing on a single table*/ + @schema_name sysname = NULL, /*use when focusing on a single table/view, or to a single schema with no table name*/ + @table_name sysname = NULL, /*use when focusing on a single table or view*/ @min_reads bigint = 0, /*only look at indexes with a minimum number of reads*/ @min_writes bigint = 0, /*only look at indexes with a minimum number of writes*/ @min_size_gb decimal(10,2) = 0, /*only look at indexes with a minimum size*/ @@ -139,7 +139,7 @@ BEGIN TRY ap.name WHEN N'@database_name' THEN 'the name of the database you wish to analyze' WHEN N'@schema_name' THEN 'limits analysis to tables in the specified schema when used without @table_name' - WHEN N'@table_name' THEN 'the table name to filter indexes by, requires @schema_name if not dbo' + WHEN N'@table_name' THEN 'the table or view name to filter indexes by, requires @schema_name if not dbo' WHEN N'@min_reads' THEN 'minimum number of reads for an index to be considered used' WHEN N'@min_writes' THEN 'minimum number of writes for an index to be considered used' WHEN N'@min_size_gb' THEN 'minimum size in GB for an index to be analyzed' @@ -159,7 +159,7 @@ BEGIN TRY ap.name WHEN N'@database_name' THEN 'the name of a database you care about indexes in' WHEN N'@schema_name' THEN 'schema name or NULL for all schemas' - WHEN N'@table_name' THEN 'table name or NULL for all tables' + WHEN N'@table_name' THEN 'table (or view) name or NULL for all tables' WHEN N'@min_reads' THEN 'any positive integer or 0' WHEN N'@min_writes' THEN 'any positive integer or 0' WHEN N'@min_size_gb' THEN 'any positive decimal or 0' @@ -1178,12 +1178,12 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. SELECT DISTINCT @database_id, database_name = DB_NAME(@database_id), - schema_id = t.schema_id, + schema_id = s.schema_id, schema_name = s.name, - object_id = t.object_id, - table_name = t.name, + object_id = i.object_id, + table_name = ISNULL(t.name, v.name), index_id = i.index_id, - index_name = ISNULL(i.name, t.name + N''.Heap''), + index_name = ISNULL(i.name, ISNULL(t.name, v.name) + N''.Heap''), can_compress = CASE WHEN p.index_id > 0 @@ -1191,26 +1191,21 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. THEN 1 ELSE 0 END - FROM ' + QUOTENAME(@current_database_name) + N'.sys.tables AS t + FROM ' + QUOTENAME(@current_database_name) + N'.sys.indexes AS i + LEFT JOIN ' + QUOTENAME(@current_database_name) + N'.sys.tables AS t + ON i.object_id = t.object_id + LEFT JOIN ' + QUOTENAME(@current_database_name) + N'.sys.views AS v + ON i.object_id = v.object_id JOIN ' + QUOTENAME(@current_database_name) + N'.sys.schemas AS s - ON t.schema_id = s.schema_id - JOIN ' + QUOTENAME(@current_database_name) + N'.sys.indexes AS i - ON t.object_id = i.object_id + ON ISNULL(t.schema_id, v.schema_id) = s.schema_id JOIN ' + QUOTENAME(@current_database_name) + N'.sys.partitions AS p ON i.object_id = p.object_id AND i.index_id = p.index_id LEFT JOIN ' + QUOTENAME(@current_database_name) + N'.sys.dm_db_index_usage_stats AS us - ON t.object_id = us.object_id + ON i.object_id = us.object_id AND us.database_id = @database_id - WHERE t.is_ms_shipped = 0 - AND t.type <> N''TF'' - AND NOT EXISTS - ( - SELECT - 1/0 - FROM ' + QUOTENAME(@current_database_name) + N'.sys.views AS v - WHERE v.object_id = i.object_id - ) + WHERE (t.object_id IS NULL OR t.is_ms_shipped = 0) + AND (t.object_id IS NULL OR t.type <> N''TF'') AND i.is_disabled = 0 AND i.is_hypothetical = 0'; @@ -1261,7 +1256,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. END; SET @sql += N' - AND t.object_id = @object_id'; + AND i.object_id = @object_id'; END; IF @schema_name IS NOT NULL @@ -1284,7 +1279,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. FROM ' + QUOTENAME(@current_database_name) + N'.sys.dm_db_partition_stats AS ps JOIN ' + QUOTENAME(@current_database_name) + N'.sys.allocation_units AS au ON ps.partition_id = au.container_id - WHERE ps.object_id = t.object_id + WHERE ps.object_id = i.object_id GROUP BY ps.object_id HAVING @@ -1295,7 +1290,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. SELECT 1/0 FROM ' + QUOTENAME(@current_database_name) + N'.sys.dm_db_partition_stats AS ps - WHERE ps.object_id = t.object_id + WHERE ps.object_id = i.object_id AND ps.index_id IN (0, 1) GROUP BY ps.object_id @@ -1307,7 +1302,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. SELECT 1/0 FROM ' + QUOTENAME(@current_database_name) + N'.sys.dm_db_index_usage_stats AS ius - WHERE ius.object_id = t.object_id + WHERE ius.object_id = i.object_id AND ius.database_id = @database_id GROUP BY ius.object_id @@ -1716,9 +1711,9 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. schema_id = s.schema_id, schema_name = s.name, os.object_id, - table_name = t.name, + table_name = ISNULL(t.name, v.name), os.index_id, - index_name = ISNULL(i.name, t.name + N''.Heap''), + index_name = ISNULL(i.name, ISNULL(t.name, v.name) + N''.Heap''), range_scan_count = SUM(os.range_scan_count), singleton_lookup_count = SUM(os.singleton_lookup_count), forwarded_fetch_count = SUM(os.forwarded_fetch_count), @@ -1756,10 +1751,12 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. NULL, NULL ) AS os - JOIN ' + QUOTENAME(@current_database_name) + N'.sys.tables AS t + LEFT JOIN ' + QUOTENAME(@current_database_name) + N'.sys.tables AS t ON os.object_id = t.object_id + LEFT JOIN ' + QUOTENAME(@current_database_name) + N'.sys.views AS v + ON os.object_id = v.object_id JOIN ' + QUOTENAME(@current_database_name) + N'.sys.schemas AS s - ON t.schema_id = s.schema_id + ON ISNULL(t.schema_id, v.schema_id) = s.schema_id JOIN ' + QUOTENAME(@current_database_name) + N'.sys.indexes AS i ON os.object_id = i.object_id AND os.index_id = i.index_id @@ -1777,7 +1774,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. s.schema_id, s.name, os.object_id, - t.name, + ISNULL(t.name, v.name), os.index_id, i.name OPTION(RECOMPILE); @@ -1867,12 +1864,12 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. SELECT database_id = @database_id, database_name = DB_NAME(@database_id), - t.object_id, + i.object_id, i.index_id, s.schema_id, schema_name = s.name, - table_name = t.name, - index_name = ISNULL(i.name, t.name + N''.Heap''), + table_name = ISNULL(t.name, v.name), + index_name = ISNULL(i.name, ISNULL(t.name, v.name) + N''.Heap''), column_name = c.name, column_id = c.column_id, i.is_primary_key, @@ -1964,11 +1961,13 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ) THEN 0 END - FROM ' + QUOTENAME(@current_database_name) + N'.sys.tables AS t + FROM ' + QUOTENAME(@current_database_name) + N'.sys.indexes AS i + LEFT JOIN ' + QUOTENAME(@current_database_name) + N'.sys.tables AS t + ON i.object_id = t.object_id + LEFT JOIN ' + QUOTENAME(@current_database_name) + N'.sys.views AS v + ON i.object_id = v.object_id JOIN ' + QUOTENAME(@current_database_name) + N'.sys.schemas AS s - ON t.schema_id = s.schema_id - JOIN ' + QUOTENAME(@current_database_name) + N'.sys.indexes AS i - ON t.object_id = i.object_id + ON ISNULL(t.schema_id, v.schema_id) = s.schema_id JOIN ' + QUOTENAME(@current_database_name) + N'.sys.index_columns AS ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id @@ -1983,7 +1982,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ON i.object_id = us.object_id AND i.index_id = us.index_id AND us.database_id = @database_id - WHERE t.is_ms_shipped = 0 + WHERE (t.object_id IS NULL OR t.is_ms_shipped = 0) AND i.type IN (1, 2) AND i.is_disabled = 0 AND i.is_hypothetical = 0 @@ -1993,7 +1992,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 1/0 FROM #filtered_objects AS fo WHERE fo.database_id = @database_id - AND fo.object_id = t.object_id + AND fo.object_id = i.object_id ) AND EXISTS ( @@ -2005,7 +2004,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ( nvarchar(MAX), N'.sys.dm_db_partition_stats ps - WHERE ps.object_id = t.object_id + WHERE ps.object_id = i.object_id AND ps.index_id = 1 AND ps.row_count >= @min_rows )' @@ -2019,7 +2018,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. END; SELECT @sql += N' - AND t.object_id = @object_id'; + AND i.object_id = @object_id'; END; SELECT @@ -2148,8 +2147,8 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ps.index_id, s.schema_id, schema_name = s.name, - table_name = t.name, - index_name = ISNULL(i.name, t.name + N''.Heap''), + table_name = ISNULL(t.name, v.name), + index_name = ISNULL(i.name, ISNULL(t.name, v.name) + N''.Heap''), ps.partition_id, p.partition_number, total_rows = ps.row_count, @@ -2158,11 +2157,13 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. reserved_row_overflow_gb = SUM(ps.row_overflow_reserved_page_count) * 8. / 1024. / 1024.0, /* Convert directly to GB */ p.data_compression_desc, i.data_space_id - FROM ' + QUOTENAME(@current_database_name) + N'.sys.tables AS t - JOIN ' + QUOTENAME(@current_database_name) + N'.sys.indexes AS i - ON t.object_id = i.object_id + FROM ' + QUOTENAME(@current_database_name) + N'.sys.indexes AS i + LEFT JOIN ' + QUOTENAME(@current_database_name) + N'.sys.tables AS t + ON i.object_id = t.object_id + LEFT JOIN ' + QUOTENAME(@current_database_name) + N'.sys.views AS v + ON i.object_id = v.object_id JOIN ' + QUOTENAME(@current_database_name) + N'.sys.schemas AS s - ON t.schema_id = s.schema_id + ON ISNULL(t.schema_id, v.schema_id) = s.schema_id JOIN ' + QUOTENAME(@current_database_name) + N'.sys.partitions AS p ON i.object_id = p.object_id AND i.index_id = p.index_id @@ -2170,7 +2171,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ON p.partition_id = a.container_id LEFT HASH JOIN ' + QUOTENAME(@current_database_name) + N'.sys.dm_db_partition_stats AS ps ON p.partition_id = ps.partition_id - WHERE t.type <> N''TF'' + WHERE (t.object_id IS NULL OR t.type <> N''TF'') AND i.type IN (1, 2) AND EXISTS ( @@ -2178,7 +2179,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 1/0 FROM #filtered_objects AS fo WHERE fo.database_id = @database_id - AND fo.object_id = t.object_id + AND fo.object_id = i.object_id )'; IF @object_id IS NOT NULL @@ -2189,7 +2190,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. END; SELECT @sql += N' - AND t.object_id = @object_id'; + AND i.object_id = @object_id'; END; SELECT @@ -2199,7 +2200,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ps.index_id, s.schema_id, s.name, - t.name, + ISNULL(t.name, v.name), i.name, ps.partition_id, p.partition_number, diff --git a/sp_QuickieStore/README.md b/sp_QuickieStore/README.md index 59ccd568..0e3532e1 100644 --- a/sp_QuickieStore/README.md +++ b/sp_QuickieStore/README.md @@ -61,7 +61,7 @@ Use the `@expert_mode` parameter to return additional details. | @query_type | varchar | filter for only ad hoc queries or only from queries from modules | ad hoc, adhoc, proc, procedure, whatever. | NULL | | @expert_mode | bit | returns additional columns and results | 0 or 1 | 0 | | @hide_help_table | bit | hides the "bottom table" that shows help and support information | 0 or 1 | 0 | -| @format_output | bit | returns numbers formatted with commas | 0 or 1 | 1 | +| @format_output | bit | returns numbers formatted with commas and most decimals rounded away | 0 or 1 | 1 | | @get_all_databases | bit | looks for query store enabled user databases and returns combined results from all of them | 0 or 1 | 0 | | @include_databases | nvarchar(4000) | comma-separated list of databases to include (only when @get_all_databases = 1) | a string; comma separated database names | NULL | | @exclude_databases | nvarchar(4000) | comma-separated list of databases to exclude (only when @get_all_databases = 1) | a string; comma separated database names | NULL | diff --git a/sp_QuickieStore/sp_QuickieStore.sql b/sp_QuickieStore/sp_QuickieStore.sql index bcf4856a..e3168cb6 100644 --- a/sp_QuickieStore/sp_QuickieStore.sql +++ b/sp_QuickieStore/sp_QuickieStore.sql @@ -88,7 +88,7 @@ ALTER PROCEDURE @query_type varchar(11) = NULL, /*filter for only ad hoc queries or only from queries from modules*/ @expert_mode bit = 0, /*returns additional columns and results*/ @hide_help_table bit = 0, /*hides the "bottom table" that shows help and support information*/ - @format_output bit = 1, /*returns numbers formatted with commas*/ + @format_output bit = 1, /*returns numbers formatted with commas and most decimals rounded away*/ @get_all_databases bit = 0, /*looks for query store enabled user databases and returns combined results from all of them*/ @include_databases nvarchar(max) = NULL, /*comma-separated list of databases to include (only when @get_all_databases = 1)*/ @exclude_databases nvarchar(max) = NULL, /*comma-separated list of databases to exclude (only when @get_all_databases = 1)*/ @@ -204,7 +204,7 @@ BEGIN WHEN N'@query_type' THEN 'filter for only ad hoc queries or only from queries from modules' WHEN N'@expert_mode' THEN 'returns additional columns and results' WHEN N'@hide_help_table' THEN 'hides the "bottom table" that shows help and support information' - WHEN N'@format_output' THEN 'returns numbers formatted with commas' + WHEN N'@format_output' THEN 'returns numbers formatted with commas and most decimals rounded away' WHEN N'@get_all_databases' THEN 'looks for query store enabled user databases and returns combined results from all of them' WHEN N'@include_databases' THEN 'comma-separated list of databases to include (only when @get_all_databases = 1)' WHEN N'@exclude_databases' THEN 'comma-separated list of databases to exclude (only when @get_all_databases = 1)' @@ -442,6 +442,222 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. RETURN; END; /*End @help section*/ + +/* +Validate Sort Order. +We do this super early on, because we care about it even +when populating the tables that we declare very soon. +*/ +IF @sort_order NOT IN + ( + 'cpu', + 'logical reads', + 'physical reads', + 'writes', + 'duration', + 'memory', + 'tempdb', + 'executions', + 'recent', + 'plan count by hashes', + 'cpu waits', + 'lock waits', + 'locks waits', + 'latch waits', + 'latches waits', + 'buffer latch waits', + 'buffer latches waits', + 'buffer io waits', + 'log waits', + 'log io waits', + 'network waits', + 'network io waits', + 'parallel waits', + 'parallelism waits', + 'memory waits', + 'total waits', + 'rows' + ) +BEGIN + RAISERROR('The sort order (%s) you chose is so out of this world that I''m using cpu instead', 10, 1, @sort_order) WITH NOWAIT; + + SELECT + @sort_order = 'cpu'; +END; + +DECLARE + @sort_order_is_a_wait bit; + +/* +Checks if the sort order is for a wait. +Cuts out a lot of repetition. +*/ +IF LOWER(@sort_order) IN + ( + 'cpu waits', + 'lock waits', + 'locks waits', + 'latch waits', + 'latches waits', + 'buffer latch waits', + 'buffer latches waits', + 'buffer io waits', + 'log waits', + 'log io waits', + 'network waits', + 'network io waits', + 'parallel waits', + 'parallelism waits', + 'memory waits', + 'total waits' + ) +BEGIN + SELECT + @sort_order_is_a_wait = 1; +END; + +/* +We also validate regression mode super early. +We need to do this here so we can build @ColumnDefinitions correctly. +It also lets us fail fast, if needed. +*/ +DECLARE + @regression_mode bit; + +/* +Set @regression_mode if the given arguments indicate that +we are checking for regressed queries. +Also set any default parameters for regression mode while we're at it. +*/ +IF @regression_baseline_start_date IS NOT NULL +BEGIN + SELECT + @regression_mode = 1, + @regression_comparator = + ISNULL(@regression_comparator, 'absolute'), + @regression_direction = + ISNULL(@regression_direction, 'regressed'); +END; + +/* +Error out if the @regression parameters do not make sense. +*/ +IF +( + @regression_baseline_start_date IS NULL + AND + ( + @regression_baseline_end_date IS NOT NULL + OR @regression_comparator IS NOT NULL + OR @regression_direction IS NOT NULL + ) +) +BEGIN + RAISERROR('@regression_baseline_start_date is mandatory if you have specified any other @regression_ parameter.', 11, 1) WITH NOWAIT; + RETURN; +END; + +/* +Error out if the @regression_baseline_start_date and +@regression_baseline_end_date are incompatible. +We could try and guess a sensible resolution, but +I do not think that we can know what people want. +*/ +IF +( + @regression_baseline_start_date IS NOT NULL +AND @regression_baseline_end_date IS NOT NULL +AND @regression_baseline_start_date >= @regression_baseline_end_date +) +BEGIN + RAISERROR('@regression_baseline_start_date has been set greater than or equal to @regression_baseline_end_date. +This does not make sense. Check that the values of both parameters are as you intended them to be.', 11, 1) WITH NOWAIT; + RETURN; +END; + +/* +Validate @regression_comparator. +*/ +IF +( + @regression_comparator IS NOT NULL +AND @regression_comparator NOT IN ('relative', 'absolute') +) +BEGIN + RAISERROR('The regression_comparator (%s) you chose is so out of this world that I''m using ''absolute'' instead', 10, 1, @regression_comparator) WITH NOWAIT; + + SELECT + @regression_comparator = 'absolute'; +END; + +/* +Validate @regression_direction. +*/ +IF +( + @regression_direction IS NOT NULL +AND @regression_direction NOT IN ('regressed', 'worse', 'improved', 'better', 'magnitude', 'absolute') +) +BEGIN + RAISERROR('The regression_direction (%s) you chose is so out of this world that I''m using ''regressed'' instead', 10, 1, @regression_direction) WITH NOWAIT; + + SELECT + @regression_direction = 'regressed'; +END; + +/* +Error out if we're trying to do regression mode with 'recent' +as our @sort_order. How could that ever make sense? +*/ +IF +( + @regression_mode = 1 +AND @sort_order = 'recent' +) +BEGIN + RAISERROR('Your @sort_order is ''recent'', but you are trying to compare metrics for two time periods. +If you can imagine a useful way to do that, then make a feature request. +Otherwise, either stop specifying any @regression_ parameters or specify a different @sort_order.', 11, 1) WITH NOWAIT; + RETURN; +END; + +/* +Error out if we're trying to do regression mode with 'plan count by hashes' +as our @sort_order. How could that ever make sense? +*/ +IF +( + @regression_mode = 1 +AND @sort_order = 'plan count by hashes' +) +BEGIN + RAISERROR('Your @sort_order is ''plan count by hashes'', but you are trying to compare metrics for two time periods. +This is probably not useful, since our method of comparing two time period relies on only checking query hashes that are in both time periods. +If you can imagine a useful way to do that, then make a feature request. +Otherwise, either stop specifying any @regression_ parameters or specify a different @sort_order.', 11, 1) WITH NOWAIT; + RETURN; +END; + +/* +Error out if @regression_comparator tells us to use division, +but @regression_direction tells us to take the modulus. +It doesn't make sense to specifically ask us to remove the sign +of something that doesn't care about it. +*/ +IF +( + @regression_comparator = 'relative' +AND @regression_direction IN ('absolute', 'magnitude') +) +BEGIN + RAISERROR('Your @regression_comparator is ''relative'', but you have asked for an ''absolute'' or ''magnitude'' @regression_direction. This is probably a mistake. +Your @regression_direction tells us to take the absolute value of our result of comparing the metrics in the current time period to the baseline time period, +but your @regression_comparator is telling us to use division to compare the two time periods. This is unlikely to produce useful results. +If you can imagine a useful way to do that, then make a feature request. Otherwise, either change @regression_direction to another value +(e.g. ''better'' or ''worse'') or change @regression_comparator to ''absolute''.', 11, 1) WITH NOWAIT; + RETURN; +END; + /* These are the tables that we'll use to grab data from query store It will be fun @@ -1265,7 +1481,7 @@ CREATE TABLE id bigint IDENTITY PRIMARY KEY CLUSTERED, current_table nvarchar(100) NOT NULL, start_time datetime NOT NULL, - end_time datetime NOT NULL, + end_time datetime NULL, runtime_ms AS FORMAT ( @@ -1461,24 +1677,25 @@ BEGIN INSERT INTO @ColumnDefinitions (column_id, metric_group, metric_type, column_name, column_source, is_conditional, condition_param, condition_value, expert_only, format_pattern) VALUES - (1600, 'sort_order', 'plan_hash_count', 'plan_hash_count_for_query_hash', 'hashes.plan_hash_count_for_query_hash', 0, NULL, NULL, 0, 'N0'), - (1610, 'sort_order', 'query_hash', 'query_hash_from_hash_counting', 'hashes.query_hash', 0, NULL, NULL, 0, NULL); + /* 230, so just before avg_query_duration_ms. */ + (230, 'sort_order', 'query_hash', 'query_hash_from_hash_counting', 'hashes.query_hash', 0, NULL, NULL, 0, NULL), + (231, 'sort_order', 'plan_hash_count', 'plan_hash_count_for_query_hash', 'hashes.plan_hash_count_for_query_hash', 0, NULL, NULL, 0, 'N0'); END; /* Dynamic regression change column based on formatting and comparator */ -IF @regression_baseline_start_date IS NOT NULL AND @regression_comparator = 'relative' AND @format_output = 1 +IF @regression_mode = 1 AND @regression_comparator = 'relative' AND @format_output = 1 BEGIN INSERT INTO @ColumnDefinitions (column_id, metric_group, metric_type, column_name, column_source, is_conditional, condition_param, condition_value, expert_only, format_pattern) VALUES (160, 'regression', 'change', 'change_in_average_for_query_hash_since_regression_time_period', 'regression.change_since_regression_time_period', 1, 'regression_mode', 1, 0, 'P2'); END; -ELSE IF @regression_baseline_start_date IS NOT NULL AND @format_output = 1 +ELSE IF @regression_mode = 1 AND @format_output = 1 BEGIN INSERT INTO @ColumnDefinitions (column_id, metric_group, metric_type, column_name, column_source, is_conditional, condition_param, condition_value, expert_only, format_pattern) VALUES (160, 'regression', 'change', 'change_in_average_for_query_hash_since_regression_time_period', 'regression.change_since_regression_time_period', 1, 'regression_mode', 1, 0, 'N2'); END; -ELSE IF @regression_baseline_start_date IS NOT NULL +ELSE IF @regression_mode = 1 BEGIN INSERT INTO @ColumnDefinitions (column_id, metric_group, metric_type, column_name, column_source, is_conditional, condition_param, condition_value, expert_only, format_pattern) @@ -1486,12 +1703,13 @@ BEGIN END; /* Wait time for wait-based sorting */ -IF LOWER(@sort_order) LIKE N'%waits' +IF @sort_order_is_a_wait = 1 BEGIN INSERT INTO @ColumnDefinitions (column_id, metric_group, metric_type, column_name, column_source, is_conditional, condition_param, condition_value, expert_only, format_pattern) VALUES - (1620, 'sort_order', 'wait_time', 'total_wait_time_from_sort_order_ms', 'waits.total_query_wait_time_ms', 0, NULL, NULL, 0, 'N0'); + /* 240, so just before avg_query_duration_ms. */ + (240, 'sort_order', 'wait_time', 'total_wait_time_from_sort_order_ms', 'waits.total_query_wait_time_ms', 0, NULL, NULL, 0, 'N0'); END; /* ROW_NUMBER window function for sorting */ @@ -1504,7 +1722,7 @@ VALUES 'n', 'n', 'ROW_NUMBER() OVER (PARTITION BY qsrs.plan_id ORDER BY ' + - CASE WHEN @regression_baseline_start_date IS NOT NULL THEN + CASE WHEN @regression_mode = 1 THEN /* As seen when populating #regression_changes */ CASE @regression_direction WHEN 'regressed' THEN 'regression.change_since_regression_time_period' @@ -1527,8 +1745,8 @@ VALUES WHEN 'recent' THEN 'qsrs.last_execution_time' WHEN 'rows' THEN 'qsrs.avg_rowcount' WHEN 'plan count by hashes' THEN 'hashes.plan_hash_count_for_query_hash DESC, hashes.query_hash' - ELSE CASE WHEN LOWER(@sort_order) LIKE N'%waits' THEN 'waits.total_query_wait_time_ms' - ELSE 'qsrs.avg_cpu_time' END + ELSE CASE WHEN @sort_order_is_a_wait = 1 THEN 'waits.total_query_wait_time_ms' + ELSE 'qsrs.avg_cpu_time_ms' END END END + ' DESC)', 0, @@ -1684,10 +1902,8 @@ DECLARE @df integer, @work_start_utc time(0), @work_end_utc time(0), - @sort_order_is_a_wait bit, @regression_baseline_start_date_original datetimeoffset(7), @regression_baseline_end_date_original datetimeoffset(7), - @regression_mode bit, @regression_where_clause nvarchar(max), @column_sql nvarchar(max), @param_name nvarchar(100), @@ -1770,138 +1986,6 @@ SELECT ) ); -/* -Set @regression_mode if the given arguments indicate that -we are checking for regressed queries. -*/ -IF @regression_baseline_start_date IS NOT NULL -BEGIN - SELECT - @regression_mode = 1; -END; - -/* -Error out if the @regression parameters do not make sense. -*/ -IF -( - @regression_baseline_start_date IS NULL - AND - ( - @regression_baseline_end_date IS NOT NULL - OR @regression_comparator IS NOT NULL - OR @regression_direction IS NOT NULL - ) -) -BEGIN - RAISERROR('@regression_baseline_start_date is mandatory if you have specified any other @regression_ parameter.', 11, 1) WITH NOWAIT; - RETURN; -END; - -/* -Error out if the @regression_baseline_start_date and -@regression_baseline_end_date are incompatible. -We could try and guess a sensible resolution, but -I do not think that we can know what people want. -*/ -IF -( - @regression_baseline_start_date IS NOT NULL -AND @regression_baseline_end_date IS NOT NULL -AND @regression_baseline_start_date >= @regression_baseline_end_date -) -BEGIN - RAISERROR('@regression_baseline_start_date has been set greater than or equal to @regression_baseline_end_date. -This does not make sense. Check that the values of both parameters are as you intended them to be.', 11, 1) WITH NOWAIT; - RETURN; -END; - - -/* -Validate @regression_comparator. -*/ -IF -( - @regression_comparator IS NOT NULL -AND @regression_comparator NOT IN ('relative', 'absolute') -) -BEGIN - RAISERROR('The regression_comparator (%s) you chose is so out of this world that I''m using ''absolute'' instead', 10, 1, @regression_comparator) WITH NOWAIT; - - SELECT - @regression_comparator = 'absolute'; -END; - -/* -Validate @regression_direction. -*/ -IF -( - @regression_direction IS NOT NULL -AND @regression_direction NOT IN ('regressed', 'worse', 'improved', 'better', 'magnitude', 'absolute') -) -BEGIN - RAISERROR('The regression_direction (%s) you chose is so out of this world that I''m using ''regressed'' instead', 10, 1, @regression_direction) WITH NOWAIT; - - SELECT - @regression_direction = 'regressed'; -END; - -/* -Error out if we're trying to do regression mode with 'recent' -as our @sort_order. How could that ever make sense? -*/ -IF -( - @regression_mode = 1 -AND @sort_order = 'recent' -) -BEGIN - RAISERROR('Your @sort_order is ''recent'', but you are trying to compare metrics for two time periods. -If you can imagine a useful way to do that, then make a feature request. -Otherwise, either stop specifying any @regression_ parameters or specify a different @sort_order.', 11, 1) WITH NOWAIT; - RETURN; -END; - -/* -Error out if we're trying to do regression mode with 'plan count by hashes' -as our @sort_order. How could that ever make sense? -*/ -IF -( - @regression_mode = 1 -AND @sort_order = 'plan count by hashes' -) -BEGIN - RAISERROR('Your @sort_order is ''plan count by hashes'', but you are trying to compare metrics for two time periods. -This is probably not useful, since our method of comparing two time period relies on only checking query hashes that are in both time periods. -If you can imagine a useful way to do that, then make a feature request. -Otherwise, either stop specifying any @regression_ parameters or specify a different @sort_order.', 11, 1) WITH NOWAIT; - RETURN; -END; - - -/* -Error out if @regression_comparator tells us to use division, -but @regression_direction tells us to take the modulus. -It doesn't make sense to specifically ask us to remove the sign -of something that doesn't care about it. -*/ -IF -( - @regression_comparator = 'relative' -AND @regression_direction IN ('absolute', 'magnitude') -) -BEGIN - RAISERROR('Your @regression_comparator is ''relative'', but you have asked for an ''absolute'' or ''magnitude'' @regression_direction. This is probably a mistake. -Your @regression_direction tells us to take the absolute value of our result of comparing the metrics in the current time period to the baseline time period, -but your @regression_comparator is telling us to use division to compare the two time periods. This is unlikely to produce useful results. -If you can imagine a useful way to do that, then make a feature request. Otherwise, either change @regression_direction to another value -(e.g. ''better'' or ''worse'') or change @regression_comparator to ''absolute''.', 11, 1) WITH NOWAIT; - RETURN; -END; - - /* Set the _original variables, as we have for other values that would break inside @@ -2816,7 +2900,6 @@ END; /* As above, but for @regression_baseline_start_date and @regression_baseline_end_date. -We set the other @regression_ variables while we're at it. */ IF @regression_mode = 1 BEGIN @@ -2837,11 +2920,7 @@ We set both _date_original variables earlier. MINUTE, @utc_minutes_difference, @regression_baseline_end_date_original - ), - @regression_comparator = - ISNULL(@regression_comparator, 'absolute'), - @regression_direction = - ISNULL(@regression_direction, 'regressed'); + ); END; /* @@ -3404,74 +3483,6 @@ BEGIN @new = 1; END; -/* -Validate Sort Order -*/ -IF @sort_order NOT IN - ( - 'cpu', - 'logical reads', - 'physical reads', - 'writes', - 'duration', - 'memory', - 'tempdb', - 'executions', - 'recent', - 'plan count by hashes', - 'cpu waits', - 'lock waits', - 'locks waits', - 'latch waits', - 'latches waits', - 'buffer latch waits', - 'buffer latches waits', - 'buffer io waits', - 'log waits', - 'log io waits', - 'network waits', - 'network io waits', - 'parallel waits', - 'parallelism waits', - 'memory waits', - 'total waits', - 'rows' - ) -BEGIN - RAISERROR('The sort order (%s) you chose is so out of this world that I''m using cpu instead', 10, 1, @sort_order) WITH NOWAIT; - - SELECT - @sort_order = 'cpu'; -END; - -/* -Checks if the sort order is for a wait. -Cuts out a lot of repetition. -*/ -IF LOWER(@sort_order) IN - ( - 'cpu waits', - 'lock waits', - 'locks waits', - 'latch waits', - 'latches waits', - 'buffer latch waits', - 'buffer latches waits', - 'buffer io waits', - 'log waits', - 'log io waits', - 'network waits', - 'network io waits', - 'parallel waits', - 'parallelism waits', - 'memory waits', - 'total waits' - ) -BEGIN - SELECT - @sort_order_is_a_wait = 1; -END; - /* See if our cool new 2022 views exist. May have to tweak this if views aren't present in some cloudy situations. @@ -8533,14 +8544,24 @@ FROM qsp.all_plan_ids,' + CASE WHEN @include_plan_hashes IS NOT NULL + OR @ignore_plan_hashes IS NOT NULL + OR @sort_order = 'plan count by hashes' THEN N' qsp.query_plan_hash,' + ELSE N'' + END + + CASE WHEN @include_query_hashes IS NOT NULL + OR @ignore_query_hashes IS NOT NULL OR @sort_order = 'plan count by hashes' OR @include_query_hash_totals = 1 THEN N' qsq.query_hash,' + ELSE N'' + END + + CASE WHEN @include_sql_handles IS NOT NULL + OR @ignore_sql_handles IS NOT NULL THEN N' qsqt.statement_sql_handle,' ELSE N'' @@ -8880,13 +8901,13 @@ ORDER BY WHEN 'writes' THEN N'x.avg_logical_io_writes_mb' WHEN 'duration' THEN N'x.avg_duration_ms' WHEN 'memory' THEN N'x.avg_query_max_used_memory_mb' - WHEN 'tempdb' THEN CASE WHEN @new = 1 THEN N'x.avg_tempdb_space_used_mb' ELSE N'x.avg_cpu_time' END + WHEN 'tempdb' THEN CASE WHEN @new = 1 THEN N'x.avg_tempdb_space_used_mb' ELSE N'x.avg_cpu_time_ms' END WHEN 'executions' THEN N'x.count_executions' WHEN 'recent' THEN N'x.last_execution_time' WHEN 'rows' THEN N'x.avg_rowcount' WHEN 'plan count by hashes' THEN N'x.plan_hash_count_for_query_hash DESC, x.query_hash_from_hash_counting' - ELSE CASE WHEN @sort_order_is_a_wait = 1 THEN N'x.total_wait_time_from_sort_order_ms' ELSE N'x.avg_cpu_time' END + ELSE CASE WHEN @sort_order_is_a_wait = 1 THEN N'x.total_wait_time_from_sort_order_ms' ELSE N'x.avg_cpu_time_ms' END END END /* The ORDER BY is on the same level as the topmost SELECT, which is just SELECT x.*. @@ -8917,13 +8938,13 @@ ORDER BY WHEN 'writes' THEN N'TRY_PARSE(x.avg_logical_io_writes_mb AS money)' WHEN 'duration' THEN N'TRY_PARSE(x.avg_duration_ms AS money)' WHEN 'memory' THEN N'TRY_PARSE(x.avg_query_max_used_memory_mb AS money)' - WHEN 'tempdb' THEN CASE WHEN @new = 1 THEN N'TRY_PARSE(x.avg_tempdb_space_used_mb AS money)' ELSE N'TRY_PARSE(x.avg_cpu_time AS money)' END + WHEN 'tempdb' THEN CASE WHEN @new = 1 THEN N'TRY_PARSE(x.avg_tempdb_space_used_mb AS money)' ELSE N'TRY_PARSE(x.avg_cpu_time_ms AS money)' END WHEN 'executions' THEN N'TRY_PARSE(x.count_executions AS money)' WHEN 'recent' THEN N'x.last_execution_time' WHEN 'rows' THEN N'TRY_PARSE(x.avg_rowcount AS money)' WHEN 'plan count by hashes' THEN N'TRY_PARSE(x.plan_hash_count_for_query_hash AS money) DESC, x.query_hash_from_hash_counting' - ELSE CASE WHEN @sort_order_is_a_wait = 1 THEN N'TRY_PARSE(x.total_wait_time_from_sort_order_ms AS money)' ELSE N'TRY_PARSE(x.avg_cpu_time AS money)' END + ELSE CASE WHEN @sort_order_is_a_wait = 1 THEN N'TRY_PARSE(x.total_wait_time_from_sort_order_ms AS money)' ELSE N'TRY_PARSE(x.avg_cpu_time_ms AS money)' END END END END + N' DESC