Gathering SQL Server Performance Counters for Multiple Servers
SQL Multi Script can easily be persuaded to run queries at the server level rather than the database level. It is also able to combine results from many databases even if the results aren't identical but have some different columns. Phil Factor demonstrates how this works, when collecting a set of performance counters from all databases on a distribution list of servers.
Collecting performance counters for all databases on a server
When SQL Multi Script collects results from multiple servers, it does it intelligently, by sharing columns that are identical and providing columns with NULL
values where it can’t. It always provides columns for the basic dimensions of ‘server’ and ‘database’. You can opt to hide them in the built-in result pane.
The way that SQL Multi Script handles results that are similar but not identical is more easily explained by demonstration than words.
We’ll start with code that displays the performance counters for all the databases on a server. These performance counters are to be found in sys.dm_os_performance_counters
. This DMV has the fifty most useful performance counters for each database. If we are going to use SQL Multi Script to display this information, we need to execute it just once for each server. The most obvious way of doing this is to use master
on each server. The code to get this pivot table is as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
/* sys.dm_os_performance_counters. Returns a row per performance counter maintained by the server. For per-second counters, this value is cumulative. The rate value must be calculated by sampling the value at discrete time intervals. The difference between any two successive sample values is equal to the rate for the time interval used.*/ DECLARE @command NVARCHAR(MAX)='' SELECT @command=@Command+command from (SELECT DISTINCT 'sum(CASE WHEN instance_name='''+RTrim(lower(instance_name)) +''' THEN cntr_value ELSE 0 END) AS ['++RTrim(lower(instance_name))+'], ' AS command FROM sys.dm_os_performance_counters WHERE [Object_Name] LIKE '%:Databases%' )f select @command /* first gather up all the names of the databases, and do a pivot rotation by doing a GROUP BY with case statements in each aggregation sum(CASE WHEN instance_name='_Total' THEN cntr_value ELSE 0 END) AS [_Total], sum(CASE WHEN instance_name='NorthWind' THEN cntr_value ELSE 0 END) AS [NorthWind], sum(CASE WHEN instance_name='AdventureWorks2016' THEN cntr_value ELSE 0 END) AS [AdventureWorks2016], ...etc... now we insert that string into the command and execute it. We interpret the five different counter types used just in case the name of the counter type isn't informative enough */ DECLARE @TheFullCommand NVARCHAR(MAX)= 'SELECT @@ServerName as server, rtrim(counter_name) as [Counter_Name], [Counter_Type], '+@command+ ''''' as [ ] FROM sys.dm_os_performance_counters pcs LEFT OUTER JOIN (VALUES (65792,''no. items''), (272696576,''rate per sec.''), (537003264,''average bulk''), (1073874176, ''average count''), (1073939712, ''large raw base''))f(TheCounter,[Counter_type]) ON f.TheCounter=pcs.cntr_type WHERE [Object_Name] LIKE ''%:Databases%'' GROUP BY counter_name, [Counter_type]' EXECUTE (@TheFullCommand ) |
We can test this query out on our favourite dev server, with the following results:
Collecting counters from multiple servers
The next thing to do is to create a list of servers in SQL Multi Script. You use a list like that for any code that operates at server level.
Here, I’ve added three servers to a server distribution list called JustTheServers, and now I am ready to execute any query I want on those servers. I then select our query to list the performance counters for all the databases and execute the query by hitting ‘Execute Now’ (F5).
Following is the single, combined resultset. Each row of results relates to a database on a server, and you will see NULL
s where the database in the column doesn’t appear in the server in the row. Beware, though! SQL Multi Script does a case-sensitive comparison to decide whether the database names are the same, so you have to provide a value that is cased consistently It is easiest just to use all lowercase or all uppercase.
I’ve opted out of ‘Include Database Name in Results’ but this will only exclude these columns in the display, not the file saved from the result.
All these databases are there on Philf07, but in the screenshot below, you’ll see that the same isn’t true of secondworld
or redgate_sqldatacatalog
. Instead, there are NULL
s. Basically, all columns from the query results are represented in the final result. They have values in them if values were returned for that database, otherwise there are NULL
s.
Reading the results using PowerShell
If you were going to save this, you’d probably want to add a record that gives the date or name the file with the date. This file is easily read into any office application, or PowerShell, and digested as required. Here is a simple example of its use in PowerShell.
1 2 3 4 5 6 |
cls $MultiScriptResult=get-content 'S:\work\programs\sql\MultiCounter.csv' $Multi=ConvertFrom-Csv $MultiScriptResult #show the counters for just one database wherever it is found $multi|select 'Server Name',Counter_name,'adeliza'| where {$_.adeliza -ne 'NULL'} |
Here’s the result:
In the same way, you can get the totals for all performance counters on each server:
1 2 3 4 5 6 |
cls $MultiScriptResult=get-content 'S:\work\programs\sql\MultiCounter.csv' $Multi=ConvertFrom-Csv $MultiScriptResult #show the totals for the performance counters for each server $multi|select 'Server Name',Counter_name,'_total'| where {$_._total -ne 'NULL'} |
…or compare counter values for two databases on a server:
1 2 3 4 5 6 |
cls $MultiScriptResult=get-content 'S:\work\programs\sql\MultiCounter.csv' $Multi=ConvertFrom-Csv $MultiScriptResult #Compare the performance counters for two databases on the same named server $multi|select 'Server Name',Counter_name,'customers', 'AdventureWorks2016' | where {$_.'server name' -eq 'philf01'} |
Summary
SQL Multi Select is just a tool for executing as many SQL Query scripts as you require on a list of databases, as fast as possible, and aggregating the results. It has value for doing those many ad-hoc or one-off monitoring tasks that aren’t worth automating, even when dealing with results that are pivot tables rather than relational tables. SQL Multi Script encourages the collecting instinct: it isn’t particularly useful by itself but requires a library of useful routines to run in it.
What sort of query you’d consider useful to execute on a list of databases depends on your role. It could be as simple as reporting on the last backup or doing ‘attack surface’ checks of encryption settings, keys, system databases, authentication settings, and service accounts.
Tools in this post
SQL Toolbelt Essentials
The industry-standard tools for SQL Server development and deployment.