Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Lite/Models/ServerConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,13 @@ public bool UseWindowsAuth
/// </summary>
public bool MultiSubnetFailover { get; set; } = false;

/// <summary>
/// User databases to skip in per-database collectors (query_store, file_io_stats, etc.).
/// System databases (master/tempdb/model/msdb) and the connection database itself are always
/// excluded by the collectors and aren't represented here.
/// </summary>
public System.Collections.Generic.List<string> ExcludedDatabases { get; set; } = new();

/// <summary>
/// Server name with "(Read-Only)" suffix when ReadOnlyIntent is enabled.
/// Used for sidebar subtitle and status text.
Expand Down
13 changes: 12 additions & 1 deletion Lite/Services/RemoteCollectorService.DatabaseSize.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ private async Task<int> CollectDatabaseSizeStatsAsync(ServerConnection server, C
var serverStatus = _serverManager.GetConnectionStatus(server.Id);
bool isAzureSqlDb = serverStatus?.SqlEngineEdition == 5;

const string onPremQuery = @"
string onPremQuery = @"
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SET NOCOUNT ON;

Expand All @@ -52,6 +52,7 @@ FROM sys.databases AS d
WHERE d.state_desc = N'ONLINE'
AND d.database_id > 0
AND HAS_DBACCESS(d.name) = 1
/*EXCLUSION_FILTER_CURSOR*/
ORDER BY
d.name;

Expand Down Expand Up @@ -131,11 +132,19 @@ LEFT JOIN #file_space AS fs
ON fs.database_id = mf.database_id
AND fs.file_id = mf.file_id
WHERE d.state_desc = N'ONLINE'
/*EXCLUSION_FILTER_OUTER*/
ORDER BY
d.name,
mf.file_id
OPTION(RECOMPILE);";

/* Both filter sites (cursor SELECT and final SELECT) are in outer T-SQL, not nested dynamic SQL,
so parameter bindings work fine and the same @excl_db_N can be referenced twice. */
var (dbSizeExclusionClause, _) = BuildDatabaseExclusionFilter(server.ExcludedDatabases, "d.name");
onPremQuery = onPremQuery
.Replace("/*EXCLUSION_FILTER_CURSOR*/", dbSizeExclusionClause)
.Replace("/*EXCLUSION_FILTER_OUTER*/", dbSizeExclusionClause);

const string azureSqlDbQuery = @"
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

Expand Down Expand Up @@ -231,6 +240,8 @@ ORDER BY
using var sqlConnection = await CreateConnectionAsync(server, cancellationToken);
using var command = new SqlCommand(onPremQuery, sqlConnection);
command.CommandTimeout = CommandTimeoutSeconds;
var (_, dbSizeExclusionParams) = BuildDatabaseExclusionFilter(server.ExcludedDatabases, "d.name");
foreach (var p in dbSizeExclusionParams) command.Parameters.Add(p);

using var reader = await command.ExecuteReaderAsync(cancellationToken);
while (await reader.ReadAsync(cancellationToken))
Expand Down
7 changes: 7 additions & 0 deletions Lite/Services/RemoteCollectorService.FileIo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,13 @@ LEFT JOIN sys.databases AS d
WHERE (vfs.database_id > 4 OR vfs.database_id = 2)
AND vfs.database_id < 32761
AND vfs.database_id <> ISNULL(DB_ID(N'PerformanceMonitor'), 0)
/*EXCLUSION_FILTER*/
OPTION(RECOMPILE);";

/* Azure path filters via GetAzureDatabaseListAsync; on-prem path injects here */
var (fileIoExclusionClause, _) = BuildDatabaseExclusionFilter(server.ExcludedDatabases, "d.name");
query = query.Replace("/*EXCLUSION_FILTER*/", isAzureSqlDb ? string.Empty : fileIoExclusionClause);

var serverId = GetServerId(server);
var collectionTime = DateTime.UtcNow;
var rowsCollected = 0;
Expand Down Expand Up @@ -125,6 +130,8 @@ AND vfs.database_id < 32761
{
using var sqlConnection = await CreateConnectionAsync(server, cancellationToken);
using var command = new SqlCommand(query, sqlConnection) { CommandTimeout = CommandTimeoutSeconds };
var (_, fileIoExclusionParams) = BuildDatabaseExclusionFilter(server.ExcludedDatabases, "d.name");
foreach (var p in fileIoExclusionParams) command.Parameters.Add(p);
using var reader = await command.ExecuteReaderAsync(cancellationToken);
while (await reader.ReadAsync(cancellationToken))
fileStats.Add(ReadFileIoRow(reader));
Expand Down
15 changes: 14 additions & 1 deletion Lite/Services/RemoteCollectorService.ProcedureStats.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ so the standard NOT IN filter excludes everything. Use a simplified query. */
/* total_spills/min_spills/max_spills exist in dm_exec_procedure_stats and dm_exec_trigger_stats
on all supported versions, but do NOT exist in dm_exec_function_stats on any version.
Use dynamic SQL to handle this. */
const string standardQuery = @"
string standardQuery = @"
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

DECLARE
Expand Down Expand Up @@ -81,6 +81,7 @@ INNER JOIN sys.databases AS d
WHERE d.state = 0
AND pa.dbid NOT IN (1, 3, 4, 32761, 32767, ISNULL(DB_ID(N''PerformanceMonitor''), 0))
AND s.last_execution_time >= DATEADD(MINUTE, -10, GETDATE())
/*EXCLUSION_FILTER*/

UNION ALL

Expand Down Expand Up @@ -136,6 +137,7 @@ INNER JOIN sys.databases AS d
WHERE d.state = 0
AND pa.dbid NOT IN (1, 3, 4, 32761, 32767, ISNULL(DB_ID(N''PerformanceMonitor''), 0))
AND s.last_execution_time >= DATEADD(MINUTE, -10, GETDATE())
/*EXCLUSION_FILTER*/

UNION ALL

Expand Down Expand Up @@ -178,6 +180,7 @@ INNER JOIN sys.databases AS d
WHERE d.state = 0
AND pa.dbid NOT IN (1, 3, 4, 32761, 32767, ISNULL(DB_ID(N''PerformanceMonitor''), 0))
AND s.last_execution_time >= DATEADD(MINUTE, -10, GETDATE())
/*EXCLUSION_FILTER*/
) AS combined
ORDER BY total_elapsed_time DESC
OPTION(RECOMPILE);' AS nvarchar(max));
Expand Down Expand Up @@ -220,9 +223,19 @@ No trigger stats or function stats — Azure SQL DB scope is single-database. */
FROM sys.dm_exec_procedure_stats AS s
WHERE s.database_id = DB_ID()
AND s.last_execution_time >= DATEADD(MINUTE, -10, GETDATE())
/*EXCLUSION_FILTER*/
ORDER BY s.total_elapsed_time DESC
OPTION(RECOMPILE);";

/* Standard query is dynamic SQL (built into @sql then passed to sp_executesql), so the
exclusion filter is interpolated as literal N'...' values rather than parameter bindings.
Names come from a user-picked checklist of existing DBs, escaped against single-quote
injection. forNestedDynamicSql=true doubles the escape because @sql is itself a
single-quoted T-SQL string at the outer layer. */
string standardExclusionClause = BuildDatabaseExclusionLiteralClause(
server.ExcludedDatabases, "d.name", forNestedDynamicSql: true);
standardQuery = standardQuery.Replace("/*EXCLUSION_FILTER*/", standardExclusionClause);

string query = isAzureSqlDb ? azureSqlDbQuery : standardQuery;

var serverId = GetServerId(server);
Expand Down
14 changes: 14 additions & 0 deletions Lite/Services/RemoteCollectorService.QuerySnapshots.cs
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,18 @@ private async Task<int> CollectQuerySnapshotsAsync(ServerConnection server, Canc

var query = BuildQuerySnapshotsQuery(supportsLiveQueryPlan, isAzureSqlDatabase);

/* Append the per-database exclusion filter to the WHERE clause. The base query joins
sys.databases AS d, so we filter on d.name. When ExcludedDatabases is empty the
clause is "" so nothing changes. */
var (qsExclusionClause, _) = BuildDatabaseExclusionFilter(server.ExcludedDatabases, "d.name");
if (!string.IsNullOrEmpty(qsExclusionClause))
{
/* Inject just before the OPTION clause so it lands inside the WHERE. */
query = query.Replace(
"ORDER BY der.cpu_time DESC",
qsExclusionClause + "\nORDER BY der.cpu_time DESC");
}

var serverId = GetServerId(server);
var collectionTime = DateTime.UtcNow;
var rowsCollected = 0;
Expand All @@ -242,6 +254,8 @@ private async Task<int> CollectQuerySnapshotsAsync(ServerConnection server, Canc
using var sqlConnection = await CreateConnectionAsync(server, cancellationToken);
using var command = new SqlCommand(query, sqlConnection);
command.CommandTimeout = CommandTimeoutSeconds;
var (_, qsExclusionParams) = BuildDatabaseExclusionFilter(server.ExcludedDatabases, "d.name");
foreach (var p in qsExclusionParams) command.Parameters.Add(p);

using var reader = await command.ExecuteReaderAsync(cancellationToken);
sqlSw.Stop();
Expand Down
11 changes: 10 additions & 1 deletion Lite/Services/RemoteCollectorService.QueryStats.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Use a simplified query that skips plan_attributes entirely — there's only one
var serverStatus = _serverManager.GetConnectionStatus(server.Id);
bool isAzureSqlDb = serverStatus.SqlEngineEdition == 5;

const string standardQuery = @"
string standardQuery = @"
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

SELECT /* PerformanceMonitorLite */ TOP (200)
Expand Down Expand Up @@ -106,10 +106,14 @@ INNER JOIN sys.databases AS d
WHERE pa.dbid NOT IN (1, 2, 3, 4, 32761, 32767, ISNULL(DB_ID(N'PerformanceMonitor'), 0))
AND st.text NOT LIKE N'%PerformanceMonitorLite%'
AND qs.last_execution_time >= DATEADD(MINUTE, -10, GETDATE())
/*EXCLUSION_FILTER*/
ORDER BY
qs.total_elapsed_time DESC
OPTION(RECOMPILE);";

var (exclusionClause, _) = BuildDatabaseExclusionFilter(server.ExcludedDatabases, "d.name");
standardQuery = standardQuery.Replace("/*EXCLUSION_FILTER*/", exclusionClause);

/* Azure SQL DB: skip plan_attributes, use DB_NAME() for the single database context */
const string azureSqlDbQuery = @"
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
Expand Down Expand Up @@ -229,6 +233,11 @@ qs.total_elapsed_time DESC
{
using var command = new SqlCommand(query, sqlConnection);
command.CommandTimeout = CommandTimeoutSeconds;
if (!isAzureSqlDb)
{
var (_, freshParams) = BuildDatabaseExclusionFilter(server.ExcludedDatabases, "d.name");
foreach (var p in freshParams) command.Parameters.Add(p);
}

using var reader = await command.ExecuteReaderAsync(cancellationToken);

Expand Down
12 changes: 10 additions & 2 deletions Lite/Services/RemoteCollectorService.QueryStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ Uses sys.database_query_store_options.actual_state instead of
var serverStatus = _serverManager.GetConnectionStatus(server.Id);
bool isAzureSqlDb = serverStatus?.SqlEngineEdition == 5;

const string onPremDbQuery = @"
string onPremDbQuery = @"
SET NOCOUNT ON;
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

Expand Down Expand Up @@ -60,6 +60,7 @@ AND d.name <> N'PerformanceMonitor'
drs.database_id IS NULL /*not in any AG*/
OR drs.is_primary_replica = 1 /*primary replica*/
)
/*EXCLUSION_FILTER*/
OPTION(RECOMPILE);

OPEN db_check;
Expand Down Expand Up @@ -103,7 +104,7 @@ FROM @result
ORDER BY
name;";

const string azureDbQuery = @"
string azureDbQuery = @"
SET NOCOUNT ON;
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

Expand All @@ -123,6 +124,7 @@ WHERE d.database_id > 4
AND d.database_id < 32761
AND d.state_desc = N'ONLINE'
AND d.name <> N'PerformanceMonitor'
/*EXCLUSION_FILTER*/
OPTION(RECOMPILE);

OPEN db_check;
Expand Down Expand Up @@ -166,6 +168,10 @@ FROM @result
ORDER BY
name;";

var (exclusionClause, _) = BuildDatabaseExclusionFilter(server.ExcludedDatabases, "d.name");
onPremDbQuery = onPremDbQuery.Replace("/*EXCLUSION_FILTER*/", exclusionClause);
azureDbQuery = azureDbQuery.Replace("/*EXCLUSION_FILTER*/", exclusionClause);

string dbQuery = isAzureSqlDb ? azureDbQuery : onPremDbQuery;

var serverId = GetServerId(server);
Expand All @@ -187,6 +193,8 @@ ORDER BY
using (var dbCommand = new SqlCommand(dbQuery, sqlConnection))
{
dbCommand.CommandTimeout = CommandTimeoutSeconds;
var (_, exclusionParams) = BuildDatabaseExclusionFilter(server.ExcludedDatabases, "d.name");
foreach (var p in exclusionParams) dbCommand.Parameters.Add(p);
using var dbReader = await dbCommand.ExecuteReaderAsync(cancellationToken);
while (await dbReader.ReadAsync(cancellationToken))
{
Expand Down
17 changes: 15 additions & 2 deletions Lite/Services/RemoteCollectorService.ServerConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@ private async Task<int> CollectDatabaseConfigAsync(ServerConnection server, Canc
is_optimized_locking_on = d.is_optimized_locking_on";
}

var (dbConfigExclusionClause, _) = BuildDatabaseExclusionFilter(server.ExcludedDatabases, "d.name");

var query = $@"
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

Expand All @@ -158,6 +160,7 @@ FROM sys.databases AS d
WHERE (d.database_id > 4 OR d.database_id = 2)
AND d.database_id < 32761
AND d.name <> N'PerformanceMonitor'
{dbConfigExclusionClause}
ORDER BY d.name
OPTION(RECOMPILE);";

Expand All @@ -173,6 +176,8 @@ ORDER BY d.name
using var sqlConnection = await CreateConnectionAsync(server, cancellationToken);
using var command = new SqlCommand(query, sqlConnection);
command.CommandTimeout = CommandTimeoutSeconds;
var (_, dbConfigExclusionParams) = BuildDatabaseExclusionFilter(server.ExcludedDatabases, "d.name");
foreach (var p in dbConfigExclusionParams) command.Parameters.Add(p);

using var reader = await command.ExecuteReaderAsync(cancellationToken);
while (await reader.ReadAsync(cancellationToken))
Expand Down Expand Up @@ -329,7 +334,7 @@ private async Task<int> CollectDatabaseScopedConfigAsync(ServerConnection server
var serverStatus = _serverManager.GetConnectionStatus(server.Id);
bool isAzureSqlDb = serverStatus?.SqlEngineEdition == 5;

const string onPremDbQuery = @"
string onPremDbQuery = @"
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

SELECT
Expand All @@ -347,10 +352,11 @@ AND d.name <> N'PerformanceMonitor'
drs.database_id IS NULL /*not in any AG*/
OR drs.is_primary_replica = 1 /*primary replica*/
)
/*EXCLUSION_FILTER*/
ORDER BY d.name
OPTION(RECOMPILE);";

const string azureDbQuery = @"
string azureDbQuery = @"
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

SELECT
Expand All @@ -360,9 +366,14 @@ FROM sys.databases AS d
AND d.database_id < 32761
AND d.name <> N'PerformanceMonitor'
AND d.state_desc = N'ONLINE'
/*EXCLUSION_FILTER*/
ORDER BY d.name
OPTION(RECOMPILE);";

var (scopedExclusionClause, _) = BuildDatabaseExclusionFilter(server.ExcludedDatabases, "d.name");
onPremDbQuery = onPremDbQuery.Replace("/*EXCLUSION_FILTER*/", scopedExclusionClause);
azureDbQuery = azureDbQuery.Replace("/*EXCLUSION_FILTER*/", scopedExclusionClause);

string dbQuery = isAzureSqlDb ? azureDbQuery : onPremDbQuery;

var serverId = GetServerId(server);
Expand All @@ -379,6 +390,8 @@ ORDER BY d.name
using (var dbCommand = new SqlCommand(dbQuery, sqlConnection))
{
dbCommand.CommandTimeout = CommandTimeoutSeconds;
var (_, scopedExclusionParams) = BuildDatabaseExclusionFilter(server.ExcludedDatabases, "d.name");
foreach (var p in scopedExclusionParams) dbCommand.Parameters.Add(p);
using var dbReader = await dbCommand.ExecuteReaderAsync(cancellationToken);
while (await dbReader.ReadAsync(cancellationToken))
{
Expand Down
8 changes: 7 additions & 1 deletion Lite/Services/RemoteCollectorService.WaitingTasks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public partial class RemoteCollectorService
/// </summary>
private async Task<int> CollectWaitingTasksAsync(ServerConnection server, CancellationToken cancellationToken)
{
const string query = @"
string query = @"
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

SELECT /* PerformanceMonitorLite */
Expand All @@ -42,8 +42,12 @@ LEFT JOIN sys.databases AS d
AND wt.session_id <> @@SPID
AND wt.wait_type IS NOT NULL
AND er.database_id <> ISNULL(DB_ID(N'PerformanceMonitor'), 0)
/*EXCLUSION_FILTER*/
OPTION(RECOMPILE);";

var (exclusionClause, _) = BuildDatabaseExclusionFilter(server.ExcludedDatabases, "d.name");
query = query.Replace("/*EXCLUSION_FILTER*/", exclusionClause);

var serverId = GetServerId(server);
var collectionTime = DateTime.UtcNow;
var rowsCollected = 0;
Expand All @@ -54,6 +58,8 @@ AND wt.wait_type IS NOT NULL
using var sqlConnection = await CreateConnectionAsync(server, cancellationToken);
using var command = new SqlCommand(query, sqlConnection);
command.CommandTimeout = CommandTimeoutSeconds;
var (_, exclusionParams) = BuildDatabaseExclusionFilter(server.ExcludedDatabases, "d.name");
foreach (var p in exclusionParams) command.Parameters.Add(p);

using var reader = await command.ExecuteReaderAsync(cancellationToken);
sqlSw.Stop();
Expand Down
Loading
Loading