Jump to content

Featured Replies

Posted

The ability to measure CPU percentage for each active query can be useful to identify an issue, or set up alerting for abnormal usage.

 

 

 

Trying to identify the top queries consuming high CPU within SQL is not as simple as viewing a DMV; every active query can be viewed in the dynamic management view sys.dm_exec_requests. This view records the total elapsed time (total_elapsed_time) and the total CPU time (cpu_time).

 

 

 

As the name infers, the total elapsed time is the overall duration of the query. The column cpu_time may show a value larger than the overall duration, this is because a query may use more than one thread, this is called parallelism.

 

 

 

For an example, the below table shows 4 queries running against a SQL Server with 8 cores.

 

 

 


Query

CPU Time (ms)

Available CPU Time (ms)

CPU Usage (%)

Query 1

600

800

75%

Query 2

200

1600

20%

Query 3

1200

2400

50%

Query 4

320

3200

10%

 

 

 

With 8 processors available, each millisecond has 8 CPU milliseconds available. If a query runs for 400ms and uses 320ms of cpu_time, overall, it uses 10% of the available CPU (3200ms).

 

 

 

The document Troubleshoot high-CPU-usage issues in SQL Server (under Step 2) shows how to identify if the CPU percentage is high and how to identify the top 10 queries by CPU time. As mentioned above, the cpu_time value is no indication of high CPU usage.

 

 

 

These queries can be adapted to provide an average CPU percentage based on the overall duration.

 

 

 

The below query consists of two sections. Section 1 stores the count of active schedulers available. Section 2 then compares the cpu_time based on the available cpu_time (total_elasped_time * count of schedulers) to calculate the CPU percentage.

 

 

 

 

 

--SECTION 1 - Count of available schedulers

DECLARE @CPU_Count INT

SELECT @CPU_Count = COUNT(*)

FROM sys.dm_os_schedulers

WHERE status = 'VISIBLE ONLINE'

 

--SECTION 2 Top 10 Queries running by CPU Percentage

SELECT TOP 10

s.session_id,

r.status,

r.cpu_time,

r.logical_reads,

r.reads,

r.writes,

r.total_elapsed_time / (1000 * 60) 'Elaps M',

100.0 / (r.total_elapsed_time * @CPU_Count) * r.cpu_time Average_CPU_Percentage,

SUBSTRING(st.TEXT, (r.statement_start_offset / 2) + 1,

((CASE r.statement_end_offset

WHEN -1 THEN DATALENGTH(st.TEXT)

ELSE r.statement_end_offset

END - r.statement_start_offset) / 2) + 1) AS statement_text,

COALESCE(QUOTENAME(DB_NAME(st.dbid)) + N'.' + QUOTENAME(OBJECT_SCHEMA_NAME(st.objectid, st.dbid))

+ N'.' + QUOTENAME(OBJECT_NAME(st.objectid, st.dbid)), '') AS command_text,

r.command,

s.login_name,

s.host_name,

s.program_name,

s.last_request_end_time,

s.login_time,

r.open_transaction_count

FROM sys.dm_exec_sessions AS s

JOIN sys.dm_exec_requests AS r

ON r.session_id = s.session_id

CROSS APPLY sys.Dm_exec_sql_text(r.sql_handle) AS st

WHERE r.session_id != @@SPID

ORDER BY Average_CPU_Percentage DESC

 

 

 

 

 

Example results below.

 

 

 

largevv2px999.png.910b65676ac1ba1d740bd2e7dffda0df.png

 

 

 

Rather than getting an average CPU percentage, a more accurate CPU percentage for each query can be provided by capturing the current active queries, waiting for a set time and then capturing the active queries again, comparing the cpu_time difference to the available cpu_time.

 

 

 

The below query is similar to the above example, but CPU time will be captured for each query into a temporary table, then, after a defined delay, the CPU time will be captured again and used to calculate CPU over the duration it ran for. This example requires the query to be running the whole time, so reducing the delay is more likely to provide more results.

 

 

 

 

 

--Section 1 - Count of available schedulers

DECLARE @CPU_Count INT

SELECT @CPU_Count = COUNT(*)

FROM sys.dm_os_schedulers

WHERE status = 'VISIBLE ONLINE'

 

--Section 2 - Define Duration in seconds

DECLARE

@Delay INT,

@DelayMS INT

 

SET @Delay = 5--5

SET @DelayMS = @Delay * 1000

 

--Section 3 - Capture active queries and current cpu_time now

SELECT

session_id,

start_time,

cpu_time

INTO #RunningQueries

FROM sys.dm_exec_requests

 

--Section 4 - Wait For 5 Seconds

WAITFOR DELAY @Delay

 

--Section 5 - Compare against active queries now

SELECT TOP 10

s.session_id,

r.status,

r.cpu_time,

r.logical_reads,

r.reads,

r.writes,

r.total_elapsed_time / (1000 * 60) 'Elaps M',

CONVERT(DECIMAL(5,2), (100.0 / (@DelayMS * @CPU_Count)) * (r.cpu_time - rq.cpu_time)) CPU_Percentage,

SUBSTRING(st.TEXT, (r.statement_start_offset / 2) + 1,

((CASE r.statement_end_offset

WHEN -1 THEN DATALENGTH(st.TEXT)

ELSE r.statement_end_offset

END - r.statement_start_offset) / 2) + 1) AS statement_text,

COALESCE(QUOTENAME(DB_NAME(st.dbid)) + N'.' + QUOTENAME(OBJECT_SCHEMA_NAME(st.objectid, st.dbid))

+ N'.' + QUOTENAME(OBJECT_NAME(st.objectid, st.dbid)), '') AS command_text,

r.command,

s.login_name,

s.host_name,

s.program_name,

s.last_request_end_time,

s.login_time,

r.open_transaction_count

FROM sys.dm_exec_sessions AS s

JOIN sys.dm_exec_requests AS r

ON r.session_id = s.session_id

CROSS APPLY sys.Dm_exec_sql_text(r.sql_handle) AS st

JOIN #RunningQueries rq

ON r.session_id = rq.session_id

AND r.start_time = rq.start_time

WHERE r.session_id != @@SPID

ORDER BY CPU_Percentage DESC

 

--Section 6 - Drop Temporary Table

DROP TABLE #RunningQueries

 

 

 

 

 

Example results below.

 

 

 

largevv2px999.png.6b2493dc1835fe87613f096fd8d0633a.png

 

 

 

This can also be used as a stored procedure to run either scenario depending on the variable values provided.

 

 

 

 

 

CREATE PROCEDURE pCaptureQueryCPUPercent

@Mode BIT, --0 = AVG CPU, 1 = Current CPU

@DelayDuration INT = 5 --Default 5 seconds

 

AS

 

/*

Procedure pCaptureQueryCPUPercent

 

Examples:-

 

--Capture average CPU percentage since each query began

EXECUTE pCaptureQueryCPUPercent 0

 

--Capture average CPU percentage over 2 seconds.

EXECUTE pCaptureQueryCPUPercent 1, 2

*/

 

SET NOCOUNT ON

 

--Count of available schedulers

DECLARE @CPU_Count INT

SELECT @CPU_Count = COUNT(*)

FROM sys.dm_os_schedulers

WHERE status = 'VISIBLE ONLINE'

 

IF @Mode = 0

BEGIN

--SECTION 2 Top 10 Queries running by CPU Percentage

SELECT TOP 10

s.session_id,

r.status,

r.cpu_time,

r.logical_reads,

r.reads,

r.writes,

r.total_elapsed_time / (1000 * 60) 'Elaps M',

100.0 / (r.total_elapsed_time * @CPU_Count) * r.cpu_time Average_CPU_Percentage,

SUBSTRING(st.TEXT, (r.statement_start_offset / 2) + 1,

((CASE r.statement_end_offset

WHEN -1 THEN DATALENGTH(st.TEXT)

ELSE r.statement_end_offset

END - r.statement_start_offset) / 2) + 1) AS statement_text,

COALESCE(QUOTENAME(DB_NAME(st.dbid)) + N'.' + QUOTENAME(OBJECT_SCHEMA_NAME(st.objectid, st.dbid))

+ N'.' + QUOTENAME(OBJECT_NAME(st.objectid, st.dbid)), '') AS command_text,

r.command,

s.login_name,

s.host_name,

s.program_name,

s.last_request_end_time,

s.login_time,

r.open_transaction_count

FROM sys.dm_exec_sessions AS s

JOIN sys.dm_exec_requests AS r

ON r.session_id = s.session_id

CROSS APPLY sys.Dm_exec_sql_text(r.sql_handle) AS st

WHERE r.session_id != @@SPID

ORDER BY Average_CPU_Percentage DESC

END

 

IF @Mode = 1 --Current CPU

BEGIN

 

--Define Duration in seconds

DECLARE

@DelayDurationMS INT

 

SET @DelayDurationMS = @DelayDuration * 1000

 

--Capture active queries and current cpu_time now

SELECT

session_id,

start_time,

cpu_time

INTO #RunningQueries

FROM sys.dm_exec_requests

 

--Wait For 5 Seconds

WAITFOR DELAY @DelayDuration

 

--Compare against active queries now

SELECT TOP 10

s.session_id,

r.status,

r.cpu_time,

r.logical_reads,

r.reads,

r.writes,

r.total_elapsed_time / (1000 * 60) 'Elaps M',

CONVERT(DECIMAL(5,2), (100.0 / (@DelayDurationMS * @CPU_Count)) * (r.cpu_time - rq.cpu_time)) CPU_Percentage,

SUBSTRING(st.TEXT, (r.statement_start_offset / 2) + 1,

((CASE r.statement_end_offset

WHEN -1 THEN DATALENGTH(st.TEXT)

ELSE r.statement_end_offset

END - r.statement_start_offset) / 2) + 1) AS statement_text,

COALESCE(QUOTENAME(DB_NAME(st.dbid)) + N'.' + QUOTENAME(OBJECT_SCHEMA_NAME(st.objectid, st.dbid))

+ N'.' + QUOTENAME(OBJECT_NAME(st.objectid, st.dbid)), '') AS command_text,

r.command,

s.login_name,

s.host_name,

s.program_name,

s.last_request_end_time,

s.login_time,

r.open_transaction_count

FROM sys.dm_exec_sessions AS s

JOIN sys.dm_exec_requests AS r

ON r.session_id = s.session_id

CROSS APPLY sys.Dm_exec_sql_text(r.sql_handle) AS st

JOIN #RunningQueries rq

ON r.session_id = rq.session_id

AND r.start_time = rq.start_time

WHERE r.session_id != @@SPID

ORDER BY CPU_Percentage DESC

 

--Drop Temporary Table

DROP TABLE #RunningQueries

END

 

 

 

I hope this article was helpful for you, please like it on this page and share through social media. please feel free to share your feedback in the comments section below.

 

Continue reading...

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...