The script to get statistics about the performance of your SQL storage for period of time. It will capture metrics like latency, number of reads/writes and average size for each database files on on the sql instance.
<#
.SYNOPSIS
Get-SQLIOLatencies
.DESCRIPTION
The script to get statistics about the performance of your SQL storage for
period of time. It will capture metrics like latency, number of reads/writes
and average size for each database files on on the sql instance. The script
can save the result in CSV if CSVPath is provided and the result will be
shown in Grid View. If CSVPath parameter is not provided the output will be
listed for each request as type System.Data.DataRow.
The script will impersonate the Windows user that is running the script in
order to authenticate against the SQL! You can use SQL Authentication by
providing parameter UserID and Password!
The script is based on sql script by Paul S. Randal from sqlskills.com, I
transformed this script po PowerShell with sole purpose to make it easier to
run and receive useful output in PowerShell. I have permission from Paul to
make and publish this powershell version. See Paul's copyright below.
You can see the original sql script here:
http://www.sqlskills.com/blogs/paul/capturing-io-latencies-period-time/
Author: Ivan Yankulov [Senior SharePoint Engineer @ bluesource Information Limited]
Contact: http://spyankulov.blogspot.com
About this script: http://spyankulov.blogspot.com/2015/10/capture-sql-io-latencies-for-period-of.html
How it Works: http://www.sqlskills.com/blogs/paul/capturing-io-latencies-period-time/
------------------------------------------------------------------------------
Original SQL Script written by Paul S. Randal, SQLskills.com
(c) 2014, SQLskills.com. All rights reserved.
For more scripts and sample code, check out http://www.SQLskills.com
You may alter this code for your own *non-commercial* purposes (e.g. in a for-
sale commercial tool). Use in your own environment is encouraged. You may
republish altered code as long as you include this copyright and give due
credit, but you must obtain prior permission before blogging this code.
THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
------------------------------------------------------------------------------
.PARAMETER SQLServer
SQL Server name
.PARAMETER Port
SQL Port
.PARAMETER Minutes
Number of minutes to capture data for
.PARAMETER CSVPath
Full path where CSV output will be saved
.PARAMETER UserID
SQL Username if SQL Authentication will be used
.PARAMETER Password
SQL Password if SQL Authentication will be used
.EXAMPLE
.\Get-SQLIOLatencies.ps1 -SQLServer DB\SPSQL -CSVPath E:\Latency.csv -Minutes 120
This will capture data for 2 hours and the output will be CSV/GidView. For
authentication to the SQL will be used the identity that is running the
script.
.EXAMPLE
.\Get-SQLIOLatencies.ps1 -SQLServer DB\SPSQL -Minutes 60
This will capture data for 1 hour and the output will be in powershell only.
For authentication to the SQL will be used the identity that is running the
script.
.EXAMPLE
.\Get-SQLIOLatencies.ps1 -SQLServer DB\SPSQL -CSVPath E:\Latency.csv -Minutes 120 -UserID duser -Password demo!234
This will capture data for 2 hours and the output will be CSV/GidView. For
authentication to the SQL will be used SQL Authentication with Username and
Password.
.LINK
http://spyankulov.blogspot.com
http://spyankulov.blogspot.com/2015/10/capture-sql-io-latencies-for-period-of.html
http://www.sqlskills.com/blogs/paul/capturing-io-latencies-period-time/
#>
[CmdletBinding()]
Param(
[parameter(Mandatory=$True)]
[string]$SQLServer,
[parameter(Mandatory=$True)]
[string]$Minutes,
[parameter(Mandatory=$False)]
[string]$Port,
[parameter(Mandatory=$False)]
[string]$CSVPath,
[parameter(Mandatory=$False)]
[string]$UserID,
[parameter(Mandatory=$False)]
[string]$Password
)
BEGIN{
If($Port){
$dbServer = $SQLServer + "," + $Port
}Else{
$dbServer = $SQLServer
}
If($UserID -and $Password){
$connectionString = "Data Source=$dbServer; User Id=$UserID; Password=$Password; Database=master"
}Else{
$connectionString = "Data Source=$dbServer; Integrated Security=SSPI; Database=master"
}
$waitSeconds = (New-TimeSpan -Minutes $Minutes).TotalSeconds
}
PROCESS{
$debugMSG = @"
*** CONNECTION *****
Connection String = $connectionString
Minutes = $Minutes
"@
Write-Verbose -Message $debugMSG
$sqlCommand1 = @"
IF EXISTS (SELECT * FROM [tempdb].[sys].[objects]
WHERE [name] = N'##SQLskillsStats1')
DROP TABLE [##SQLskillsStats1];
IF EXISTS (SELECT * FROM [tempdb].[sys].[objects]
WHERE [name] = N'##SQLskillsStats2')
DROP TABLE [##SQLskillsStats2]
"@
$sqlCommand2 = @"
SELECT [database_id], [file_id], [num_of_reads], [io_stall_read_ms],
[num_of_writes], [io_stall_write_ms], [io_stall],
[num_of_bytes_read], [num_of_bytes_written], [file_handle]
INTO ##SQLskillsStats1
FROM sys.dm_io_virtual_file_stats (NULL, NULL)
"@
$sqlCommand3 = @"
SELECT [database_id], [file_id], [num_of_reads], [io_stall_read_ms],
[num_of_writes], [io_stall_write_ms], [io_stall],
[num_of_bytes_read], [num_of_bytes_written], [file_handle]
INTO ##SQLskillsStats2
FROM sys.dm_io_virtual_file_stats (NULL, NULL);
"@
$sqlCommand4 = @"
WITH [DiffLatencies] AS
(SELECT
-- Files that weren't in the first snapshot
[ts2].[database_id],
[ts2].[file_id],
[ts2].[num_of_reads],
[ts2].[io_stall_read_ms],
[ts2].[num_of_writes],
[ts2].[io_stall_write_ms],
[ts2].[io_stall],
[ts2].[num_of_bytes_read],
[ts2].[num_of_bytes_written]
FROM [##SQLskillsStats2] AS [ts2]
LEFT OUTER JOIN [##SQLskillsStats1] AS [ts1]
ON [ts2].[file_handle] = [ts1].[file_handle]
WHERE [ts1].[file_handle] IS NULL
UNION
SELECT
-- Diff of latencies in both snapshots
[ts2].[database_id],
[ts2].[file_id],
[ts2].[num_of_reads] - [ts1].[num_of_reads] AS [num_of_reads],
[ts2].[io_stall_read_ms] - [ts1].[io_stall_read_ms] AS [io_stall_read_ms],
[ts2].[num_of_writes] - [ts1].[num_of_writes] AS [num_of_writes],
[ts2].[io_stall_write_ms] - [ts1].[io_stall_write_ms] AS [io_stall_write_ms],
[ts2].[io_stall] - [ts1].[io_stall] AS [io_stall],
[ts2].[num_of_bytes_read] - [ts1].[num_of_bytes_read] AS [num_of_bytes_read],
[ts2].[num_of_bytes_written] - [ts1].[num_of_bytes_written] AS [num_of_bytes_written]
FROM [##SQLskillsStats2] AS [ts2]
LEFT OUTER JOIN [##SQLskillsStats1] AS [ts1]
ON [ts2].[file_handle] = [ts1].[file_handle]
WHERE [ts1].[file_handle] IS NOT NULL)
SELECT
DB_NAME ([vfs].[database_id]) AS [DB],
LEFT ([mf].[physical_name], 2) AS [Drive],
[mf].[type_desc],
[num_of_reads] AS [Reads],
[num_of_writes] AS [Writes],
[ReadLatency(ms)] =
CASE WHEN [num_of_reads] = 0
THEN 0 ELSE ([io_stall_read_ms] / [num_of_reads]) END,
[WriteLatency(ms)] =
CASE WHEN [num_of_writes] = 0
THEN 0 ELSE ([io_stall_write_ms] / [num_of_writes]) END,
/*[Latency] =
CASE WHEN ([num_of_reads] = 0 AND [num_of_writes] = 0)
THEN 0 ELSE ([io_stall] / ([num_of_reads] + [num_of_writes])) END,*/
[AvgBPerRead] =
CASE WHEN [num_of_reads] = 0
THEN 0 ELSE ([num_of_bytes_read] / [num_of_reads]) END,
[AvgBPerWrite] =
CASE WHEN [num_of_writes] = 0
THEN 0 ELSE ([num_of_bytes_written] / [num_of_writes]) END,
/*[AvgBPerTransfer] =
CASE WHEN ([num_of_reads] = 0 AND [num_of_writes] = 0)
THEN 0 ELSE
(([num_of_bytes_read] + [num_of_bytes_written]) /
([num_of_reads] + [num_of_writes])) END,*/
[mf].[physical_name]
FROM [DiffLatencies] AS [vfs]
JOIN sys.master_files AS [mf]
ON [vfs].[database_id] = [mf].[database_id]
AND [vfs].[file_id] = [mf].[file_id]
-- ORDER BY [ReadLatency(ms)] DESC
ORDER BY [WriteLatency(ms)] DESC;
"@
$sqlCommand5 = @"
-- Cleanup
IF EXISTS (SELECT * FROM [tempdb].[sys].[objects]
WHERE [name] = N'##SQLskillsStats1')
DROP TABLE [##SQLskillsStats1];
IF EXISTS (SELECT * FROM [tempdb].[sys].[objects]
WHERE [name] = N'##SQLskillsStats2')
DROP TABLE [##SQLskillsStats2];
"@
$connection = New-Object System.Data.SqlClient.SqlConnection
$connection.ConnectionString = $connectionString
$connection.Open()
$command = $connection.CreateCommand()
$command.CommandText = $sqlCommand1
$command.ExecuteNonQuery() | Out-Null
$command.CommandText = $sqlCommand2
$command.ExecuteNonQuery() | Out-Null
[int]$max = $waitSeconds
for ($i = $max; $i -gt 1; $i--) {
Write-Progress -Activity "Gathering Stats" -SecondsRemaining $i
Start-Sleep 1
}
$command.CommandText = $sqlCommand3
$command.ExecuteNonQuery() | Out-Null
$command.CommandText = $sqlCommand4
$adapter = New-Object System.Data.sqlclient.sqlDataAdapter $command
$dataset = New-Object System.Data.DataSet
$adapter.Fill($dataSet) | Out-Null
If($CSVPath){
$dataSet.Tables[0] | Export-Csv -NoTypeInformation -Path $CSVPath
$dataSet.Tables[0] | Out-GridView -Title $CSVPath
}Else{
Write-Output $dataSet.Tables[0]
}
$command.CommandText = $sqlCommand5
$command.ExecuteNonQuery() | Out-Null
$connection.Close()
}