Skip to content

Commit dc0a945

Browse files
Add CDC capture exclusion to long-running query alerts (#1096)
The long-running-query alert flags any live request whose total_elapsed_time exceeds the threshold. CDC capture runs as a continuous SQL Agent session (EXEC sys.sp_MScdc_capture_job -> sys.sp_cdc_scan), so it permanently trips the alert and none of the four existing wait_type-based exclusions catch it. Add a fifth toggle, LongRunningQueryExcludeCdc (default on), consistent with the existing exclusion checkboxes. It filters on request text (sp_MScdc_capture_job / sp_cdc_scan) rather than program_name so it stays CDC-specific and does not hide unrelated Agent jobs. The predicate is NULL-safe so encrypted-module queries (NULL sql text) remain visible. Reported in discussion #1090. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 55532ca commit dc0a945

5 files changed

Lines changed: 18 additions & 3 deletions

File tree

Dashboard/MainWindow.xaml.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1454,7 +1454,7 @@ private async Task CheckAllServerAlertsAsync()
14541454
var connectionString = server.GetConnectionString(_credentialService);
14551455
var databaseService = new DatabaseService(connectionString);
14561456
var connStatus = _serverManager.GetConnectionStatus(server.Id);
1457-
var health = await databaseService.GetAlertHealthAsync(connStatus.SqlEngineEdition, prefs.LongRunningQueryThresholdMinutes, prefs.LongRunningJobMultiplier, prefs.LongRunningQueryMaxResults, prefs.LongRunningQueryExcludeSpServerDiagnostics, prefs.LongRunningQueryExcludeWaitFor, prefs.LongRunningQueryExcludeBackups, prefs.LongRunningQueryExcludeMiscWaits, prefs.AlertExcludedDatabases);
1457+
var health = await databaseService.GetAlertHealthAsync(connStatus.SqlEngineEdition, prefs.LongRunningQueryThresholdMinutes, prefs.LongRunningJobMultiplier, prefs.LongRunningQueryMaxResults, prefs.LongRunningQueryExcludeSpServerDiagnostics, prefs.LongRunningQueryExcludeWaitFor, prefs.LongRunningQueryExcludeBackups, prefs.LongRunningQueryExcludeMiscWaits, prefs.LongRunningQueryExcludeCdc, prefs.AlertExcludedDatabases);
14581458

14591459
if (health.IsOnline)
14601460
{

Dashboard/Models/UserPreferences.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ public class UserPreferences
102102
public bool LongRunningQueryExcludeWaitFor { get; set; } = true;
103103
public bool LongRunningQueryExcludeBackups { get; set; } = true;
104104
public bool LongRunningQueryExcludeMiscWaits { get; set; } = true;
105+
public bool LongRunningQueryExcludeCdc { get; set; } = true; // Exclude CDC capture jobs (sp_MScdc_capture_job / sp_cdc_scan)
105106
public bool NotifyOnTempDbSpace { get; set; } = true;
106107
public int TempDbSpaceThresholdPercent { get; set; } = 80; // Alert when TempDB used > X%
107108
public bool NotifyOnLongRunningJobs { get; set; } = true;

Dashboard/Services/DatabaseService.NocHealth.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ public async Task<AlertHealthResult> GetAlertHealthAsync(
131131
bool excludeWaitFor = true,
132132
bool excludeBackups = true,
133133
bool excludeMiscWaits = true,
134+
bool excludeCdc = true,
134135
IReadOnlyList<string>? excludedDatabases = null)
135136
{
136137
var result = new AlertHealthResult();
@@ -149,7 +150,7 @@ public async Task<AlertHealthResult> GetAlertHealthAsync(
149150
? GetFilteredDeadlockCountAsync(connection, excludedDatabases)
150151
: null;
151152
var poisonWaitTask = GetPoisonWaitDeltasAsync(connection);
152-
var longRunningTask = GetLongRunningQueriesAsync(connection, longRunningQueryThresholdMinutes, longRunningQueryMaxResults, excludeSpServerDiagnostics, excludeWaitFor, excludeBackups, excludeMiscWaits);
153+
var longRunningTask = GetLongRunningQueriesAsync(connection, longRunningQueryThresholdMinutes, longRunningQueryMaxResults, excludeSpServerDiagnostics, excludeWaitFor, excludeBackups, excludeMiscWaits, excludeCdc);
153154
var tempDbTask = GetTempDbSpaceAsync(connection);
154155
var anomalousJobTask = GetAnomalousJobsAsync(connection, longRunningJobMultiplier);
155156
var missingCaptureTask = GetMissingCaptureSessionsAsync(connection);
@@ -751,7 +752,8 @@ private async Task<List<LongRunningQueryInfo>> GetLongRunningQueriesAsync(
751752
bool excludeSpServerDiagnostics = true,
752753
bool excludeWaitFor = true,
753754
bool excludeBackups = true,
754-
bool excludeMiscWaits = true)
755+
bool excludeMiscWaits = true,
756+
bool excludeCdc = true)
755757
{
756758
maxResults = Math.Clamp(maxResults, 1, 1000);
757759

@@ -763,6 +765,12 @@ private async Task<List<LongRunningQueryInfo>> GetLongRunningQueriesAsync(
763765
? "AND r.wait_type NOT IN (N'BACKUPTHREAD', N'BACKUPIO')" : "";
764766
string miscWaitsFilter = excludeMiscWaits
765767
? "AND r.wait_type NOT IN (N'XE_LIVE_TARGET_TVF')" : "";
768+
// CDC capture runs continuously as a SQL Agent job (EXEC sys.sp_MScdc_capture_job -> sys.sp_cdc_scan),
769+
// so it permanently exceeds the duration threshold. Filter on request text, not program_name, to stay
770+
// CDC-specific and avoid hiding unrelated Agent jobs (ETL, index maintenance). Keep NULL text (encrypted
771+
// modules) visible -- only drop rows we can positively identify as CDC.
772+
string cdcFilter = excludeCdc
773+
? "AND (t.text IS NULL OR (t.text NOT LIKE N'%sp_MScdc_capture_job%' AND t.text NOT LIKE N'%sp_cdc_scan%'))" : "";
766774

767775
string query = @$"SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
768776
@@ -787,6 +795,7 @@ CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) AS t
787795
{waitForFilter}
788796
{backupsFilter}
789797
{miscWaitsFilter}
798+
{cdcFilter}
790799
ORDER BY r.total_elapsed_time DESC
791800
OPTION(MAXDOP 1, RECOMPILE);";
792801

Dashboard/SettingsWindow.xaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,9 @@
246246
Margin="0,0,0,4"/>
247247
<CheckBox x:Name="LrqExcludeMiscWaitsCheckBox"
248248
Content="Exclude miscellaneous waits (XE_LIVE_TARGET_TVF)"
249+
Margin="0,0,0,4"/>
250+
<CheckBox x:Name="LrqExcludeCdcCheckBox"
251+
Content="Exclude CDC capture jobs (sp_MScdc_capture_job, sp_cdc_scan)"
249252
Margin="0,0,0,0"/>
250253
<StackPanel Orientation="Horizontal" Margin="0,8,0,0">
251254
<CheckBox x:Name="NotifyOnTempDbSpaceCheckBox"

Dashboard/SettingsWindow.xaml.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ private void LoadSettings()
186186
LrqExcludeWaitForCheckBox.IsChecked = prefs.LongRunningQueryExcludeWaitFor;
187187
LrqExcludeBackupsCheckBox.IsChecked = prefs.LongRunningQueryExcludeBackups;
188188
LrqExcludeMiscWaitsCheckBox.IsChecked = prefs.LongRunningQueryExcludeMiscWaits;
189+
LrqExcludeCdcCheckBox.IsChecked = prefs.LongRunningQueryExcludeCdc;
189190
AlertExcludedDatabasesTextBox.Text = string.Join(", ", prefs.AlertExcludedDatabases);
190191
NotifyOnTempDbSpaceCheckBox.IsChecked = prefs.NotifyOnTempDbSpace;
191192
TempDbSpaceThresholdTextBox.Text = prefs.TempDbSpaceThresholdPercent.ToString(CultureInfo.InvariantCulture);
@@ -683,6 +684,7 @@ private async void OkButton_Click(object sender, RoutedEventArgs e)
683684
prefs.LongRunningQueryExcludeWaitFor = LrqExcludeWaitForCheckBox.IsChecked == true;
684685
prefs.LongRunningQueryExcludeBackups = LrqExcludeBackupsCheckBox.IsChecked == true;
685686
prefs.LongRunningQueryExcludeMiscWaits = LrqExcludeMiscWaitsCheckBox.IsChecked == true;
687+
prefs.LongRunningQueryExcludeCdc = LrqExcludeCdcCheckBox.IsChecked == true;
686688
prefs.AlertExcludedDatabases = AlertExcludedDatabasesTextBox.Text
687689
.Split(',')
688690
.Select(s => s.Trim())

0 commit comments

Comments
 (0)