patternsqlModerate
Get IO stats request sql like activity monitor
Viewed 0 times
sqlrequeststatslikegetmonitoractivity
Problem
Is it possible to get the same value as in the activity monitor (SSMS) for the "Database IO" as a result of a SQL request?
click to enlarge
click to enlarge
Solution
You can use a query like this to calculate read/write rates and latency (though due to my laziness these figures are not in units that match Activity Monitor).
See these resources for more info:
If you want to simulate the graph and capture spikes in real time, well, you'll need to build scaffolding to collect snapshots of this (or some other) query, and compare the deltas to generate metrics. This is not something that is trivial to write for you on a Q & A site - people pay big money for high-quality monitoring tools that make this kind of thing easy. :-)
If you want to get an idea of how Activity Monitor does this kind of thing, you can get at the definition for the procedure
```
CREATE PROC #am_generate_waitstats AS
BEGIN
-- Setup query starts a tran -- make sure it wasn't orphaned
WHILE (@@TRANCOUNT > 0) COMMIT TRAN;
-- Get the most recent snapshot ID in the history table ('previous snapshot')
DECLARE @previous_snapshot_id bigint;
DECLARE @previous_collection_time datetime;
SELECT @previous_snapshot_id = ISNULL (MAX (snapshot_id), 0) FROM #am_wait_stats_snapshots;
SELECT TOP 1 @previous_collection_time = ISNULL (collection_time, GETUTCDATE()) FROM #am_wait_stats_snapshots
WHERE snapshot_id = @previous_snapshot_id;
-- The snapshot we're about to capture ('current snapshot')
DECLARE @current_snapshot_id bigint;
DECLARE @current_collection_time datetime;
SET @current_collection_time = GETUTCDATE();
SET @current_snapshot_id = @previous_snapshot_id + 1;
-- The snapshots table holds the two most recent snapshots. Delete the older of the two to
-- make room for a new snapshot.
DELETE FROM #am_wait_stats_snapshots WHERE snapshot_id = 10
THEN DATEDIFF (second, @previous_collection_time, @current_collection_time)
-- Avoid divide-by-zero
WHEN DATEDIFF (millisecond, @previous_collection_time, @current_collection_time) = 0.0
THEN 0.0001
ELSE DATEDIFF (millisecond, @previous_collection_time, @current_collection_time) / 1000.0
END;
-- This query captures in-progress and completed (cumulative) wait time for each wait type
INSERT INTO #am_wait_stats_snapshots
SELECT
@previous_snapshot_id + 1 AS snapshot_id,
@current_collection_time AS collection_time,
wait_type,
SUM (waiting_tasks_count) AS waiting_tasks_count,
SUM (signal_wait_time_ms) AS signal_wait_time_ms,
SUM (wait_time_ms) AS wait_time_ms,
SUM (raw_wait_time_ms) AS raw_wait_time_ms
FROM
(
-- global server wait stats (completed waits only)
SELECT
wait_type,
waiting_tasks_count,
(wait_time_ms - signal_wait_time_ms) AS wait_time_ms,
signal_wait_time_ms,
wait_time_ms AS raw_wait_time_ms
FROM sys.dm_os_wait_stats
WHERE waiting_tasks_count > 0 OR wait_time_ms > 0 OR signal_wait_time_ms > 0
UNION ALL
-- threads in an in-progress wait (not yet completed waits)
SELECT
wait_type,
1 AS waiting_tasks_count,
wait_duration_ms AS wait_time_ms,
0 AS signal_wait_time_ms,
wait_duration_ms AS raw_wait_time_ms
FROM sys.dm_os_waiting_tasks
-- Very brief waits quickly will roll into dm_os_wait_stats; we only need to
-- query dm_os_waiting_tasks to handle longer-lived waits.
WHERE wait_duration_ms > 1000
) AS merged_wait_stats
GROUP BY merged_wait_stats.wait_type;
/*
Now join the current snapshot to the prior snapshot to calculate the wait time for
the just-completed time interval.
The previous_snapshot derived table represents cumulative wait stats at the beginning
of the just-completed time interval, while current_snapshot is wait stats at the end of
the interval. By subtracting the start cumulative wait time from the end cumulative
wait time, we can calculate the wait time that accumulated during this time interval.
The query uses a CTE to expose the interval-specific stats that are the result of the
comparison of the current snaps
SELECT d.name, f.name, f.type_desc, f.physical_name,
[read b/ms] = num_of_bytes_read * 1.0/sample_ms,
[avg read latency] =
(1.0*s.io_stall_read_ms / (COALESCE(NULLIF(s.num_of_reads,0),1))),
[write b/ms] = num_of_bytes_written * 1.0/sample_ms,
[avg write latency] =
(1.0*s.io_stall_write_ms / (COALESCE(NULLIF(s.num_of_writes,0),1)))
FROM sys.master_files AS f
INNER JOIN sys.databases AS d
ON f.database_id = d.database_id
INNER JOIN sys.dm_io_virtual_file_stats(default, default) AS s
ON d.database_id = s.database_id
AND f.[file_id] = s.[file_id];See these resources for more info:
- What Virtual Filestats Do, and Do Not, Tell You About I/O Latency
- How to examine IO subsystem latencies from within SQL Server
- Capturing IO latencies for a period of time
- The Accidental DBA (Day 26 of 30) : Monitoring Disk I/O
- sys.dm_io_virtual_file_stats (MSDN)
If you want to simulate the graph and capture spikes in real time, well, you'll need to build scaffolding to collect snapshots of this (or some other) query, and compare the deltas to generate metrics. This is not something that is trivial to write for you on a Q & A site - people pay big money for high-quality monitoring tools that make this kind of thing easy. :-)
If you want to get an idea of how Activity Monitor does this kind of thing, you can get at the definition for the procedure
#am_generate_waitstats with a little effort. I did the heavy lifting for you and here it is in all its unformatted glory - this is how it calculates various wait statistics:```
CREATE PROC #am_generate_waitstats AS
BEGIN
-- Setup query starts a tran -- make sure it wasn't orphaned
WHILE (@@TRANCOUNT > 0) COMMIT TRAN;
-- Get the most recent snapshot ID in the history table ('previous snapshot')
DECLARE @previous_snapshot_id bigint;
DECLARE @previous_collection_time datetime;
SELECT @previous_snapshot_id = ISNULL (MAX (snapshot_id), 0) FROM #am_wait_stats_snapshots;
SELECT TOP 1 @previous_collection_time = ISNULL (collection_time, GETUTCDATE()) FROM #am_wait_stats_snapshots
WHERE snapshot_id = @previous_snapshot_id;
-- The snapshot we're about to capture ('current snapshot')
DECLARE @current_snapshot_id bigint;
DECLARE @current_collection_time datetime;
SET @current_collection_time = GETUTCDATE();
SET @current_snapshot_id = @previous_snapshot_id + 1;
-- The snapshots table holds the two most recent snapshots. Delete the older of the two to
-- make room for a new snapshot.
DELETE FROM #am_wait_stats_snapshots WHERE snapshot_id = 10
THEN DATEDIFF (second, @previous_collection_time, @current_collection_time)
-- Avoid divide-by-zero
WHEN DATEDIFF (millisecond, @previous_collection_time, @current_collection_time) = 0.0
THEN 0.0001
ELSE DATEDIFF (millisecond, @previous_collection_time, @current_collection_time) / 1000.0
END;
-- This query captures in-progress and completed (cumulative) wait time for each wait type
INSERT INTO #am_wait_stats_snapshots
SELECT
@previous_snapshot_id + 1 AS snapshot_id,
@current_collection_time AS collection_time,
wait_type,
SUM (waiting_tasks_count) AS waiting_tasks_count,
SUM (signal_wait_time_ms) AS signal_wait_time_ms,
SUM (wait_time_ms) AS wait_time_ms,
SUM (raw_wait_time_ms) AS raw_wait_time_ms
FROM
(
-- global server wait stats (completed waits only)
SELECT
wait_type,
waiting_tasks_count,
(wait_time_ms - signal_wait_time_ms) AS wait_time_ms,
signal_wait_time_ms,
wait_time_ms AS raw_wait_time_ms
FROM sys.dm_os_wait_stats
WHERE waiting_tasks_count > 0 OR wait_time_ms > 0 OR signal_wait_time_ms > 0
UNION ALL
-- threads in an in-progress wait (not yet completed waits)
SELECT
wait_type,
1 AS waiting_tasks_count,
wait_duration_ms AS wait_time_ms,
0 AS signal_wait_time_ms,
wait_duration_ms AS raw_wait_time_ms
FROM sys.dm_os_waiting_tasks
-- Very brief waits quickly will roll into dm_os_wait_stats; we only need to
-- query dm_os_waiting_tasks to handle longer-lived waits.
WHERE wait_duration_ms > 1000
) AS merged_wait_stats
GROUP BY merged_wait_stats.wait_type;
/*
Now join the current snapshot to the prior snapshot to calculate the wait time for
the just-completed time interval.
The previous_snapshot derived table represents cumulative wait stats at the beginning
of the just-completed time interval, while current_snapshot is wait stats at the end of
the interval. By subtracting the start cumulative wait time from the end cumulative
wait time, we can calculate the wait time that accumulated during this time interval.
The query uses a CTE to expose the interval-specific stats that are the result of the
comparison of the current snaps
Code Snippets
SELECT d.name, f.name, f.type_desc, f.physical_name,
[read b/ms] = num_of_bytes_read * 1.0/sample_ms,
[avg read latency] =
(1.0*s.io_stall_read_ms / (COALESCE(NULLIF(s.num_of_reads,0),1))),
[write b/ms] = num_of_bytes_written * 1.0/sample_ms,
[avg write latency] =
(1.0*s.io_stall_write_ms / (COALESCE(NULLIF(s.num_of_writes,0),1)))
FROM sys.master_files AS f
INNER JOIN sys.databases AS d
ON f.database_id = d.database_id
INNER JOIN sys.dm_io_virtual_file_stats(default, default) AS s
ON d.database_id = s.database_id
AND f.[file_id] = s.[file_id];CREATE PROC #am_generate_waitstats AS
BEGIN
-- Setup query starts a tran -- make sure it wasn't orphaned
WHILE (@@TRANCOUNT > 0) COMMIT TRAN;
-- Get the most recent snapshot ID in the history table ('previous snapshot')
DECLARE @previous_snapshot_id bigint;
DECLARE @previous_collection_time datetime;
SELECT @previous_snapshot_id = ISNULL (MAX (snapshot_id), 0) FROM #am_wait_stats_snapshots;
SELECT TOP 1 @previous_collection_time = ISNULL (collection_time, GETUTCDATE()) FROM #am_wait_stats_snapshots
WHERE snapshot_id = @previous_snapshot_id;
-- The snapshot we're about to capture ('current snapshot')
DECLARE @current_snapshot_id bigint;
DECLARE @current_collection_time datetime;
SET @current_collection_time = GETUTCDATE();
SET @current_snapshot_id = @previous_snapshot_id + 1;
-- The snapshots table holds the two most recent snapshots. Delete the older of the two to
-- make room for a new snapshot.
DELETE FROM #am_wait_stats_snapshots WHERE snapshot_id < @previous_snapshot_id;
DELETE FROM #am_resource_mon_snap WHERE current_snapshot_id < @previous_snapshot_id;
DECLARE @interval_sec numeric (20, 4);
SET @interval_sec =
CASE
-- Avoid int overflow that DATEDIFF (ms, ...) can cause in the event of a huge gap between intervals
WHEN DATEDIFF (second, @previous_collection_time, @current_collection_time) >= 10
THEN DATEDIFF (second, @previous_collection_time, @current_collection_time)
-- Avoid divide-by-zero
WHEN DATEDIFF (millisecond, @previous_collection_time, @current_collection_time) = 0.0
THEN 0.0001
ELSE DATEDIFF (millisecond, @previous_collection_time, @current_collection_time) / 1000.0
END;
-- This query captures in-progress and completed (cumulative) wait time for each wait type
INSERT INTO #am_wait_stats_snapshots
SELECT
@previous_snapshot_id + 1 AS snapshot_id,
@current_collection_time AS collection_time,
wait_type,
SUM (waiting_tasks_count) AS waiting_tasks_count,
SUM (signal_wait_time_ms) AS signal_wait_time_ms,
SUM (wait_time_ms) AS wait_time_ms,
SUM (raw_wait_time_ms) AS raw_wait_time_ms
FROM
(
-- global server wait stats (completed waits only)
SELECT
wait_type,
waiting_tasks_count,
(wait_time_ms - signal_wait_time_ms) AS wait_time_ms,
signal_wait_time_ms,
wait_time_ms AS raw_wait_time_ms
FROM sys.dm_os_wait_stats
WHERE waiting_tasks_count > 0 OR wait_time_ms > 0 OR signal_wait_time_ms > 0
UNION ALL
-- threads in an in-progress wait (not yet completed waits)
SELECT
wait_type,
1 AS waiting_tasks_count,
wait_duration_ms AS wait_time_ms,
0 AS signal_wait_time_ms,
wait_duration_ms AS raw_wait_time_ms
FROM sys.dm_os_waiting_tasks
-- Very brief waits quickly will roll into dm_os_wait_stats; we only need to
-- query dm_os_waiting_tasks to handle longer-lived waits.
WHERE wait_duration_ms > 10SET NOCOUNT ON;
DECLARE @previous_collection_time datetime;
DECLARE @previous_total_io_mb numeric (28, 1);
DECLARE @current_collection_time datetime;
DECLARE @current_total_io_mb numeric (28, 1);
DECLARE @mb_per_sec numeric (20, 1);
-- Get the previous snapshot's total I/O
SELECT TOP 1 @previous_collection_time = collection_time, @previous_total_io_mb = total_io_bytes
FROM #am_dbfileio
ORDER BY collection_time DESC;
-- Get the current total I/O.
SET @current_collection_time = GETDATE();
SELECT @current_total_io_mb = SUM(num_of_bytes_read + num_of_bytes_written) / 1024.0 / 1024.0
FROM sys.dm_io_virtual_file_stats(default, default);
-- Calc the total I/O rate (MB/sec) for the just-completed time interval.
-- Round values larger than 2MB/sec to the nearest MB.
SET @mb_per_sec = (@current_total_io_mb - @previous_total_io_mb) / DATEDIFF (millisecond, @previous_collection_time, @current_collection_time) * 1000;
IF @mb_per_sec > 2
BEGIN
SET @mb_per_sec = ROUND (@mb_per_sec, 0);
END;
-- Save off current total I/O
INSERT INTO #am_dbfileio (collection_time, total_io_bytes)
VALUES (@current_collection_time, @current_total_io_mb);
-- Return the I/O rate for the just-completed time interval.
SELECT ISNULL (@mb_per_sec, 0) AS mb_per_sec;
-- Get rid of all but the most recent snapshot's data
DELETE FROM #am_dbfileio WHERE collection_time < @current_collection_time;
DECLARE @current_collection_time datetime;
SET @current_collection_time = GETDATE();
-- Grab a snapshot
INSERT INTO #am_dbfilestats
SELECT
@current_collection_time AS collection_time,
d.name AS [Database],
f.physical_name AS [File],
(fs.num_of_bytes_read / 1024.0 / 1024.0) [Total MB Read],
(fs.num_of_bytes_written / 1024.0 / 1024.0) AS [Total MB Written],
(fs.num_of_reads + fs.num_of_writes) AS [Total I/O Count],
fs.io_stall AS [Total I/O Wait Time (ms)],
fs.size_on_disk_bytes / 1024 / 1024 AS [Size (MB)]
FROM sys.dm_io_virtual_file_stats(default, default) AS fs
INNER JOIN sys.master_files f ON fs.database_id = f.database_id AND fs.file_id = f.file_id
INNER JOIN sys.databases d ON d.database_id = fs.database_id;
-- Get the timestamp of the previous collection time
DECLARE @previous_collection_time datetime;
SELECT TOP 1 @previous_collection_time = collection_time
FROM #am_dbfilestats
WHERE collection_time < @current_collection_time
ORDER BY collection_time DESC;
DECLARE @interval_ms int;
SET @interval_ms = DATEDIFF (millisecond, @previous_collection_time, @current_collection_time);
-- Return the diff of this snapshot and last
SELECT
cur.[Database],
cur.[File] AS [File Name],
CONVERT (numeric(28,1), (cur.[Total MB Read] - prev.[Total MB Read]) * 1000 / @interval_ms) AS [MB/sec Read],
CONVERT (numeric(28,1), (cur.[Total MB Written] - prev.[Total MB Written]) * 1000 / @interval_ms) AS [MB/sec Written],
-- protect from div-by-zero
CASE
WHEN (cur.[Total I/O Count] - prev.[Total I/O Count]) = 0 THEN 0
DECLARE @current_collection_time datetime;
SET @current_collection_time = GETDATE();
-- Grab a snapshot
INSERT INTO #am_dbfilestats
SELECT
@current_collection_time AS collection_time,
d.name AS [Database],
f.physical_name AS [File],
(fs.num_of_bytes_read / 1024.0 / 1024.0) [Total MB Read],
(fs.num_of_bytes_written / 1024.0 / 1024.0) AS [Total MB Written],
(fs.num_of_reads + fs.num_of_writes) AS [Total I/O Count],
fs.io_stall AS [Total I/O Wait Time (ms)],
fs.size_on_disk_bytes / 1024 / 1024 AS [Size (MB)]
FROM sys.dm_io_virtual_file_stats(default, default) AS fs
INNER JOIN sys.master_files f ON fs.database_id = f.database_id AND fs.file_id = f.file_id
INNER JOIN sys.databases d ON d.database_id = fs.database_id;
-- Get the timestamp of the previous collection time
DECLARE @previous_collection_time datetime;
SELECT TOP 1 @previous_collection_time = collection_time
FROM #am_dbfilestats
WHERE collection_time < @current_collection_time
ORDER BY collection_time DESC;
DECLARE @interval_ms int;
SET @interval_ms = DATEDIFF (millisecond, @previous_collection_time, @current_collection_time);
-- Return the diff of this snapshot and last
SELECT
cur.[Database],
cur.[File] AS [File Name],
CONVERT (numeric(28,1), (cur.[Total MB Read] - prev.[Total MB Read]) * 1000 / @interval_ms) AS [MB/sec Read],
CONVERT (numeric(28,1), (cur.[Total MB Written] - prev.[Total MB Written]) * 1000 / @interval_ms) AS [MB/sec Written],
-- protect from div-by-zero
CASE
WHEN (cur.[Total I/O Count] - prev.[Total I/O Count]) = 0 THEN 0
ELSE
(cur.[Total I/O Wait Time (ms)] - prev.[Total I/O Wait Time (ms)])
/ (cur.[Total I/O Count] - prev.[Total I/O Count])
END AS [Response Time (ms)]
FROM #am_dbfilestats AS cur
INNER JOIN #am_dbfilestats AS prev ON prev.[Database] = cur.[Database] AND prev.[File] = cur.[File]
WHERE cur.collection_time = @current_collection_time
AND prev.collection_time = @previous_collection_time;
-- Delete the older snapshot
DELETE FROM #am_dbfilestats
WHERE collection_time != @current_collection_time;Context
StackExchange Database Administrators Q#98547, answer score: 16
Revisions (0)
No revisions yet.