In February 2018, myself and Paul Anderton gave a presentation on how to correlate database deployments with performance issues within the context of a DevOps pipeline. We used Sentry One as our monitoring tool in a Performance Test environment so that we could catch badly performing deployments before they got to production and caused havoc. If you would like to see the recorded video, then you can download it from here: http://info.sentryone.com/partner-webinar-performance-problems
As part of this presentation we had a workload running on a workstation, which executed a couple of stored procedures repeatedly, and we’ve had some requests from people to share this script. It’s a fairly crude script created specifically for this demo, but could be reused with some tweaking.
It creates a number of background jobs, and each background job calls the stored procedure with a randomized parameter value and has a randomized delay between each run.
The code for it can be downloaded here:
<#
.SYNOPSIS
Execute a stored procedure multiple times to simulate a crude workload
.DESCRIPTION
To easily create a workload by running a stored procedure multiple times in parallel, with optional randomized delays.
.PARAMETER InstanceName
The SQL Server instance name to run on.
.PARAMETER DatabaseName
The database where the stored procedure is located.
.PARAMETER StoredProcName
The name of the stored procedure to execute including the schema name
.PARAMETER ParamName
The name of the parameter to pass to the proc
.PARAMETER Threads
Number of parallel executions of the proc to run at the same time
.PARAMETER MinId
The minimum value to pass to the parameter defined in ParamName. This defines the lower bound of a randomizer
.PARAMETER MaxId
The maximum value to pass to the parameter defined in ParamName. This defines the upper bound of a randomizer
.PARAMETER MinDelay
The lower bound of the delay between executions in milliseconds
.PARAMETER MaxDelay
The upper bound of the delay between executions in milliseconds
.PARAMETER IterateSeconds
The number of seconds to run the procs in a loop before stopping
.NOTES
This proc is quite a crude way to run a simple single-valued stored procedure in a loop for demo purposes.
It could be extended to accept a hashtable of parameters.
Author: Mark Allison, Sabin.IO <home@markallison.co.uk>
.EXAMPLE
.\ExecProc_SQLClient.ps1 `
-InstanceName "SQL01" `
-DatabaseName "WideWorldImporters" `
-StoredProcName "Sales.GetBasket" `
-ParamName BasketID `
-Threads 20 `
-MinId 1 `
-MaxId 1000 `
-MinDelay 10 `
-MaxDelay 150 `
-IterateSeconds $IterateSeconds
This will run proc Sales.GetBasket with a BasketId ranging between 1 and 1000, with 20 parallel threads as background tasks. There will be a randomized delay on each thread varying between 10 and 150ms.
#>
[cmdletbinding()]
param (
[string][Parameter(Mandatory=$true)]$InstanceName,
[string][Parameter(Mandatory=$true)]$DatabaseName,
[string][Parameter(Mandatory=$true)]$StoredProcName,
[string][Parameter(Mandatory=$true)]$ParamName,
[string][Parameter(Mandatory=$true)]$Threads,
[string][Parameter(Mandatory=$true)]$minId,
[string][Parameter(Mandatory=$true)]$maxId,
[string][Parameter(Mandatory=$false)]$minDelay=10,
[string][Parameter(Mandatory=$false)]$maxDelay=100,
[int]$IterateSeconds # number of Seconds to run in a loop
)
for ($i=1; $i -le $Threads; $i++) {
Start-Job -ScriptBlock {
$StartTime = Get-Date
$conn = New-Object System.Data.SqlClient.SqlConnection
$conn.ConnectionString = "Data Source=$($using:InstanceName);Initial Catalog=$($using:DatabaseName);Integrated Security=SSPI;Application Name=PowerShell.SabinDataLoader;"
$conn.Open()
do {
$id = (Get-Random -Minimum $using:minId -Maximum $using:maxId)
$SleepMs = (Get-Random -Minimum $using:minDelay -Maximum $using:maxDelay)
$cmd = $Null
$cmd = New-Object System.Data.SqlClient.SqlCommand
$cmd.connection = $conn
$cmd.CommandType = [System.Data.CommandType]::StoredProcedure
$cmd.Parameters.AddWithValue("@$($using:ParamName)",$id) | Out-Null
$cmd.commandtext = $using:StoredProcName
$result = $cmd.executenonquery()
Start-Sleep -Milliseconds $SleepMs
}
While ((Get-Date) -le $StartTime.AddSeconds($using:IterateSeconds))
$conn.close()
}
}
#Get-Job | Wait-Job
#Get-Job | Remove-Job
For example if you want to execute a procedure called Sales.GetBasket
in parallel over 20 processes for an hour: then run it like so:
.\ExecProc_SQLClient.ps1 `
-InstanceName "SQL01" `
-DatabaseName "WideWorldImporters" `
-StoredProcName "Sales.GetBasket" `
-ParamName BasketID `
-Threads 20 `
-MinId 1 `
-MaxId 1000 `
-MinDelay 10 `
-MaxDelay 150 `
-IterateSeconds 3600
The output will look something like this:
Id Name PSJobTypeName State HasMoreData Location Command
-- ---- ------------- ----- ----------- -------- -------
1 Job1 BackgroundJob Running True localhost ...
3 Job3 BackgroundJob Running True localhost ...
5 Job5 BackgroundJob Running True localhost ...
7 Job7 BackgroundJob Running True localhost ...
9 Job9 BackgroundJob Running True localhost ...
11 Job11 BackgroundJob Running True localhost ...
13 Job13 BackgroundJob Running True localhost ...
15 Job15 BackgroundJob Running True localhost ...
17 Job17 BackgroundJob Running True localhost ...
19 Job19 BackgroundJob Running True localhost ...
21 Job21 BackgroundJob Running True localhost ...
23 Job23 BackgroundJob Running True localhost ...
25 Job25 BackgroundJob Running True localhost ...
27 Job27 BackgroundJob Running True localhost ...
29 Job29 BackgroundJob Running True localhost ...
31 Job31 BackgroundJob Running True localhost ...
33 Job33 BackgroundJob Running True localhost ...
35 Job35 BackgroundJob Running True localhost ...
37 Job37 BackgroundJob Running True localhost ...
39 Job39 BackgroundJob Running True localhost ...
41 Job41 BackgroundJob Running True localhost ...
43 Job43 BackgroundJob Running True localhost ...
45 Job45 BackgroundJob Running True localhost ...
47 Job47 BackgroundJob Running True localhost ...