Skip to content

Feature: Add alert filter by database (from PR #427)#456

Merged
erikdarlingdata merged 38 commits intodevfrom
feature/add-alert-filter-by-database
Mar 6, 2026
Merged

Feature: Add alert filter by database (from PR #427)#456
erikdarlingdata merged 38 commits intodevfrom
feature/add-alert-filter-by-database

Conversation

@erikdarlingdata
Copy link
Owner

Summary

This is PR #427 (by @HannahVernon) with merge conflicts resolved against dev and one bug fix applied:

  • Conflict resolution: Merged with PR Feature/add cooldown period setting #444 (cooldown settings) — both features coexist in Settings UI and restore-defaults
  • Bug fix: Added post-filter empty check in Dashboard BuildDeadlockContextAsync (matches the pattern in BuildBlockingContextAsync and Lite's BuildDeadlockContextAsync)

Original PR description

Adds configurable database exclusions for blocking, deadlock, and long-running query alerts in both Dashboard and Lite editions:

  • New "Global Alert Filters" section in Settings with comma-separated database exclusion list
  • Dashboard: parameterized SQL filtering for blocking + filtered deadlock count from collect.blocking_deadlock_stats
  • Lite: client-side filtering for blocking, deadlocks, and long-running queries
  • XML-based deadlock filtering — only excludes when ALL process nodes are in excluded databases (cross-DB deadlocks still fire)
  • GetFilteredDeadlockCountAsync returns null on failure, falling back to raw performance counter delta

Closes #427

🤖 Merge resolution and bug fix by Claude Code

HannahVernon and others added 30 commits February 27, 2026 14:26
…_DIAGNOSTIC wait types, and WAITFOR wait types.
…to allow future configurability on the number of long-running queries returned. Added optional parameter to control display of WAITFOR types in future.
…clusions

Feature/long running query exclusions
…c() method. Removed System.Collections.Generic using statement as it is unnecessary.
…KUPTHREAD-to-long-running-queries

Added exclusion for backup-related waits to GetLongRunningQueriesAsync() method.
…dded miscWaitsFilter to exclude XE_LIVE_TARGET_TVF waits. Removed unused parameters. Corrected minimum value for maxLongRunningQueryCount (minimum 1 instead of 5).
…KUPTHREAD-to-long-running-queries

Feature/add exclusion for backupthread to long running queries
…ITFOR, BACKUPTHREAD, BACKUPIO, and XE_LIVE_TARGET_TVF wait types in Lite/Services/LocalDataService.WaitStats.cs
…ries-monitor-changes-to-lite

Added filtering for various wait types to long running query code in Lite.
Adds LongRunningQueryMaxResults to UserPreferences (default 5) and
exposes it in the Settings UI alongside the existing duration threshold.
Threads the value through GetAlertHealthAsync and GetLongRunningQueriesAsync
to replace the hardcoded TOP(5) in the DMV query.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds Math.Clamp(1, int.MaxValue) guard to GetLongRunningQueriesAsync in
both Dashboard and Lite, and updates the Dashboard settings validation
to use an explicit range check with a descriptive error message.

Mirrors the LongRunningQueryMaxResults setting across the Lite project:
adds the App property, loads/saves it to settings.json, exposes it in
the Lite Settings UI, and passes it through to GetLongRunningQueriesAsync
to replace the hardcoded LIMIT 5.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Prevents an OverflowException if the value in settings.json is outside
the int32 range. The value is read as long, clamped to [1, int.MaxValue],
then cast back to int.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces hardcoded wait type exclusions in GetLongRunningQueriesAsync
with user-configurable booleans for SP_SERVER_DIAGNOSTICS, WAITFOR /
BROKER_RECEIVE_WAITFOR, backup waits, and miscellaneous waits. All
four filters default to true (existing behavior preserved). Settings
are exposed in the Notifications section of both Dashboard and Lite
Settings UIs and persisted to UserPreferences / settings.json.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…config-settings

Feature/long running queries config settings
…config-settings

Feature/long running queries config settings
Users can now exclude specific databases (e.g. DBAtools, msdb) from
generating alerts. The exclusion list is a comma-separated text box in
Settings > Notifications for both projects.

- Dashboard: filters LRQ at trigger level, blocking via SQL NOT IN clause,
  deadlock context filtered before email is built; list persisted in UserPreferences
- Lite: filters LRQ at trigger level; list persisted to alert_excluded_databases
  JSON array in settings.json

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The merge with dev reverted the excludedDatabases parameter from
GetAlertHealthAsync and GetBlockingValuesAsync. Re-applies those
changes on top of the dev merge (which also landed parameterised
TOP(@MaxResults) and the 1-1000 clamp in GetLongRunningQueriesAsync).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The deadlock tray notification was firing based on health.DeadlockCount,
which comes from GetDeadlockCountAsync. That query reads from
sys.dm_os_performance_counters  a server-wide counter with no
database-level granularity  so deadlocks in excluded databases still
incremented the delta and triggered the alert, even though the email
body correctly omitted them via BuildDeadlockContextAsync.

This was inconsistent with blocking and long-running query alerts, where
filtering by excluded databases happens before the threshold check.

Fix:
- Add FilteredDeadlockCount (nullable long) to AlertHealthResult to carry
  a database-filtered deadlock count alongside the raw counter value.
- Add GetFilteredDeadlockCountAsync to DatabaseService.NocHealth, which
  sums deadlock_count_delta from collect.blocking_deadlock_stats over a
  5-minute window (matching AlertCooldown), excluding the configured
  databases. This mirrors the approach used by GetBlockingValuesAsync.
- In GetAlertHealthAsync, run GetFilteredDeadlockCountAsync in parallel
  when excluded databases are configured and populate FilteredDeadlockCount.
- In EvaluateAlertConditionsAsync, use effectiveDeadlockDelta =
  FilteredDeadlockCount ?? deadlockDelta for the threshold check, tray
  notification count, RecordAlert, and email currentValue.
  The raw counter is still stored in _previousDeadlockCounts unchanged.

Result: a deadlock in an excluded database no longer triggers the tray
alert or email, consistent with how blocking alerts behave.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- In CheckPerformanceAlerts, when AlertExcludedDatabases is configured,
  fetch the raw blocked process reports and deadlock rows then count only
  those not belonging to excluded databases before comparing against the
  threshold  mirroring the existing long-running query pattern.
- Blocking: filter GetRecentBlockedProcessReportsAsync results by
  DatabaseName; use effectiveBlockingCount for threshold check,
  tray notification, and email currentValue.
- Deadlocks: filter GetRecentDeadlocksAsync results via new
  IsDeadlockExcluded helper that parses deadlock_graph_xml and excludes
  a deadlock only when every process node's currentdbname is in the
  excluded list (conservative  cross-database deadlocks still fire);
  use effectiveDeadlockCount throughout.
- Apply the same exclusion filter inside BuildBlockingContextAsync and
  BuildDeadlockContextAsync so email alert bodies also omit events from
  excluded databases.
- Add using System.Xml.Linq for XElement.Parse in the new helper.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Accidentally changed Math.Clamp upper bound from 1000 to int.MaxValue
in a prior commit. Restores the safety cap to prevent unbounded result
sets from the long-running query alert.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Both editions now filter blocking, deadlock, and long-running query
alerts by the excluded databases list. Update the settings hint text
to reflect all three alert types.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…null

Returning 0 from the catch block meant FilteredDeadlockCount was set to
a non-null value on failure, so the null-coalescing fallback in
EvaluateAlertConditionsAsync never triggered:

  var effectiveDeadlockDelta = health.FilteredDeadlockCount ?? deadlockDelta;

A 0 count silently suppressed deadlock alerts on schema errors (e.g.
missing collect.blocking_deadlock_stats), permission issues, or
timeouts. Returning null lets the fallback to the raw performance
counter delta kick in so alerts still fire.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
HannahVernon and others added 8 commits March 5, 2026 14:55
…adlockCountAsync

Replace string-interpolated NOT IN lists with dynamically built @exdb0,
@exdb1... parameters via cmd.Parameters.AddWithValue, matching the
parameterized pattern used throughout DatabaseService.*.cs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The database exclusions TextBox was nested inside the Long Running Query
Filters area, making it look like it only applied to LRQ alerts. Moved
it below all per-alert-type settings into its own 'Global Alert Filters'
section so its cross-cutting scope (blocking, deadlock, and long-running
query alerts) is visually obvious.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The deadlock filter used DB_NAME(bds.dbid) but collect.blocking_deadlock_stats
has no dbid column - it has database_name (sysname). This caused the query
to fail silently, falling back to the unfiltered delta every time.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…racy

Replace per-row DatabaseName filtering with deadlock graph XML parsing,
matching Lite's IsDeadlockExcluded approach. A deadlock is only excluded
when ALL process nodes' currentdbname are in the excluded list. This
prevents cross-database deadlocks from being silently suppressed when
only one participant's database is excluded.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…l body

The blocking threshold check already excluded filtered databases via
GetBlockingValuesAsync, but the email context could still include blocking
events from excluded databases. Now matches Lite's BuildBlockingContextAsync
which already filters.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Matches the pattern already used in BuildBlockingContextAsync (Dashboard)
and BuildDeadlockContextAsync (Lite). Without this, an email could be
sent with empty deadlock context if all returned deadlocks are filtered
out by excluded databases.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Resolve conflicts with PR #444 (cooldown settings) — both features
kept: cooldown textboxes + global alert filters section in Settings UI,
restore defaults resets both cooldowns and excluded databases.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@erikdarlingdata erikdarlingdata merged commit fcc94d3 into dev Mar 6, 2026
3 checks passed
@erikdarlingdata erikdarlingdata deleted the feature/add-alert-filter-by-database branch March 6, 2026 23:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants