From 363f9b5c4f07d42290522a7267c98b6f4aecb442 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Tue, 3 Mar 2026 09:29:33 -0500 Subject: [PATCH 1/2] =?UTF-8?q?Fix=20OverflowException=20on=20wait=20stats?= =?UTF-8?q?=20with=20large=20decimal=20values=20=E2=80=94=20Issue=20#395?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SQL Server's decimal arithmetic produces decimal(38,24) when dividing decimal(19,4) by bigint, which exceeds .NET System.Decimal's ~29 digit limit. Wrap all 20 per-second rate and avg-ms-per-wait division results in CAST(... AS decimal(18, 4)) to bound the result type. Affects wait stats, signal wait stats, lock wait stats, and execution count per-second calculations. Closes #395 Co-Authored-By: Claude Opus 4.6 --- .../DatabaseService.QueryPerformance.cs | 8 ++--- .../DatabaseService.ResourceMetrics.cs | 32 +++++++++---------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Dashboard/Services/DatabaseService.QueryPerformance.cs b/Dashboard/Services/DatabaseService.QueryPerformance.cs index 88e90457..6c3c2c81 100644 --- a/Dashboard/Services/DatabaseService.QueryPerformance.cs +++ b/Dashboard/Services/DatabaseService.QueryPerformance.cs @@ -2328,7 +2328,7 @@ GROUP BY ) SELECT ed.collection_time, - executions_per_second = CAST(ed.total_execution_count AS decimal(19, 4)) / ed.interval_seconds + executions_per_second = CAST(CAST(ed.total_execution_count AS decimal(19, 4)) / ed.interval_seconds AS decimal(18, 4)) FROM exec_deltas AS ed WHERE ed.interval_seconds > 0 ORDER BY @@ -2362,7 +2362,7 @@ GROUP BY ) SELECT ed.collection_time, - executions_per_second = CAST(ed.total_execution_count AS decimal(19, 4)) / ed.interval_seconds + executions_per_second = CAST(CAST(ed.total_execution_count AS decimal(19, 4)) / ed.interval_seconds AS decimal(18, 4)) FROM exec_deltas AS ed WHERE ed.interval_seconds > 0 ORDER BY @@ -2747,7 +2747,7 @@ WHERE ws.wait_type LIKE N'LCK%' wait_time_ms_per_second = CASE WHEN ld.interval_seconds > 0 - THEN CAST(ld.wait_time_ms_delta AS decimal(19, 4)) / ld.interval_seconds + THEN CAST(CAST(ld.wait_time_ms_delta AS decimal(19, 4)) / ld.interval_seconds AS decimal(18, 4)) ELSE 0 END FROM lock_deltas AS ld @@ -2790,7 +2790,7 @@ WHERE ws.wait_type LIKE N'LCK%' wait_time_ms_per_second = CASE WHEN ld.interval_seconds > 0 - THEN CAST(ld.wait_time_ms_delta AS decimal(19, 4)) / ld.interval_seconds + THEN CAST(CAST(ld.wait_time_ms_delta AS decimal(19, 4)) / ld.interval_seconds AS decimal(18, 4)) ELSE 0 END FROM lock_deltas AS ld diff --git a/Dashboard/Services/DatabaseService.ResourceMetrics.cs b/Dashboard/Services/DatabaseService.ResourceMetrics.cs index a6b2bc23..49120927 100644 --- a/Dashboard/Services/DatabaseService.ResourceMetrics.cs +++ b/Dashboard/Services/DatabaseService.ResourceMetrics.cs @@ -444,13 +444,13 @@ AND ws.wait_type IN (SELECT wait_type FROM top_waits) wait_time_ms_per_second = CASE WHEN wd.interval_seconds > 0 - THEN CAST(wd.wait_time_ms_delta AS decimal(19, 4)) / wd.interval_seconds + THEN CAST(CAST(wd.wait_time_ms_delta AS decimal(19, 4)) / wd.interval_seconds AS decimal(18, 4)) ELSE 0 END, signal_wait_time_ms_per_second = CASE WHEN wd.interval_seconds > 0 - THEN CAST(wd.signal_wait_time_ms_delta AS decimal(19, 4)) / wd.interval_seconds + THEN CAST(CAST(wd.signal_wait_time_ms_delta AS decimal(19, 4)) / wd.interval_seconds AS decimal(18, 4)) ELSE 0 END, wd.waiting_tasks_count @@ -521,13 +521,13 @@ AND ws.wait_type IN (SELECT wait_type FROM top_waits) wait_time_ms_per_second = CASE WHEN wd.interval_seconds > 0 - THEN CAST(wd.wait_time_ms_delta AS decimal(19, 4)) / wd.interval_seconds + THEN CAST(CAST(wd.wait_time_ms_delta AS decimal(19, 4)) / wd.interval_seconds AS decimal(18, 4)) ELSE 0 END, signal_wait_time_ms_per_second = CASE WHEN wd.interval_seconds > 0 - THEN CAST(wd.signal_wait_time_ms_delta AS decimal(19, 4)) / wd.interval_seconds + THEN CAST(CAST(wd.signal_wait_time_ms_delta AS decimal(19, 4)) / wd.interval_seconds AS decimal(18, 4)) ELSE 0 END, wd.waiting_tasks_count @@ -1879,19 +1879,19 @@ FROM collect.wait_stats AS ws wait_time_ms_per_second = CASE WHEN wd.interval_seconds > 0 - THEN CAST(wd.wait_time_ms_delta AS decimal(19, 4)) / wd.interval_seconds + THEN CAST(CAST(wd.wait_time_ms_delta AS decimal(19, 4)) / wd.interval_seconds AS decimal(18, 4)) ELSE 0 END, signal_wait_time_ms_per_second = CASE WHEN wd.interval_seconds > 0 - THEN CAST(wd.signal_wait_time_ms_delta AS decimal(19, 4)) / wd.interval_seconds + THEN CAST(CAST(wd.signal_wait_time_ms_delta AS decimal(19, 4)) / wd.interval_seconds AS decimal(18, 4)) ELSE 0 END, avg_ms_per_wait = CASE WHEN wd.waiting_tasks_delta > 0 - THEN CAST(wd.wait_time_ms_delta AS decimal(19, 4)) / wd.waiting_tasks_delta + THEN CAST(CAST(wd.wait_time_ms_delta AS decimal(19, 4)) / wd.waiting_tasks_delta AS decimal(18, 4)) ELSE 0 END FROM wait_deltas AS wd @@ -1957,19 +1957,19 @@ FROM collect.wait_stats AS ws wait_time_ms_per_second = CASE WHEN wd.interval_seconds > 0 - THEN CAST(wd.wait_time_ms_delta AS decimal(19, 4)) / wd.interval_seconds + THEN CAST(CAST(wd.wait_time_ms_delta AS decimal(19, 4)) / wd.interval_seconds AS decimal(18, 4)) ELSE 0 END, signal_wait_time_ms_per_second = CASE WHEN wd.interval_seconds > 0 - THEN CAST(wd.signal_wait_time_ms_delta AS decimal(19, 4)) / wd.interval_seconds + THEN CAST(CAST(wd.signal_wait_time_ms_delta AS decimal(19, 4)) / wd.interval_seconds AS decimal(18, 4)) ELSE 0 END, avg_ms_per_wait = CASE WHEN wd.waiting_tasks_delta > 0 - THEN CAST(wd.wait_time_ms_delta AS decimal(19, 4)) / wd.waiting_tasks_delta + THEN CAST(CAST(wd.wait_time_ms_delta AS decimal(19, 4)) / wd.waiting_tasks_delta AS decimal(18, 4)) ELSE 0 END FROM wait_deltas AS wd @@ -2066,15 +2066,15 @@ FROM collect.wait_stats AS ws wd.wait_type, wait_time_ms_per_second = CASE WHEN wd.interval_seconds > 0 - THEN CAST(wd.wait_time_ms_delta AS decimal(19, 4)) / wd.interval_seconds + THEN CAST(CAST(wd.wait_time_ms_delta AS decimal(19, 4)) / wd.interval_seconds AS decimal(18, 4)) ELSE 0 END, signal_wait_time_ms_per_second = CASE WHEN wd.interval_seconds > 0 - THEN CAST(wd.signal_wait_time_ms_delta AS decimal(19, 4)) / wd.interval_seconds + THEN CAST(CAST(wd.signal_wait_time_ms_delta AS decimal(19, 4)) / wd.interval_seconds AS decimal(18, 4)) ELSE 0 END, avg_ms_per_wait = CASE WHEN wd.waiting_tasks_delta > 0 - THEN CAST(wd.wait_time_ms_delta AS decimal(19, 4)) / wd.waiting_tasks_delta + THEN CAST(CAST(wd.wait_time_ms_delta AS decimal(19, 4)) / wd.waiting_tasks_delta AS decimal(18, 4)) ELSE 0 END FROM wait_deltas AS wd WHERE wd.wait_time_ms_delta > 0 @@ -2124,15 +2124,15 @@ FROM collect.wait_stats AS ws wd.wait_type, wait_time_ms_per_second = CASE WHEN wd.interval_seconds > 0 - THEN CAST(wd.wait_time_ms_delta AS decimal(19, 4)) / wd.interval_seconds + THEN CAST(CAST(wd.wait_time_ms_delta AS decimal(19, 4)) / wd.interval_seconds AS decimal(18, 4)) ELSE 0 END, signal_wait_time_ms_per_second = CASE WHEN wd.interval_seconds > 0 - THEN CAST(wd.signal_wait_time_ms_delta AS decimal(19, 4)) / wd.interval_seconds + THEN CAST(CAST(wd.signal_wait_time_ms_delta AS decimal(19, 4)) / wd.interval_seconds AS decimal(18, 4)) ELSE 0 END, avg_ms_per_wait = CASE WHEN wd.waiting_tasks_delta > 0 - THEN CAST(wd.wait_time_ms_delta AS decimal(19, 4)) / wd.waiting_tasks_delta + THEN CAST(CAST(wd.wait_time_ms_delta AS decimal(19, 4)) / wd.waiting_tasks_delta AS decimal(18, 4)) ELSE 0 END FROM wait_deltas AS wd WHERE wd.wait_time_ms_delta > 0 From ea694ce699437679c5dec05614734a140ca5c494 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Tue, 3 Mar 2026 09:38:16 -0500 Subject: [PATCH 2/2] Also fix decimal overflow in NocHealth poison wait avg_ms_per_wait Same decimal(19,2) / bigint => decimal(38,21) overflow risk in the poison wait delta query used by the Server Health Overview page. Co-Authored-By: Claude Opus 4.6 --- Dashboard/Services/DatabaseService.NocHealth.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dashboard/Services/DatabaseService.NocHealth.cs b/Dashboard/Services/DatabaseService.NocHealth.cs index 26cb36f0..4e711ef4 100644 --- a/Dashboard/Services/DatabaseService.NocHealth.cs +++ b/Dashboard/Services/DatabaseService.NocHealth.cs @@ -563,7 +563,7 @@ SELECT TOP (3) waiting_tasks_count_delta, avg_ms_per_wait = CASE WHEN waiting_tasks_count_delta > 0 - THEN CAST(wait_time_ms_delta AS decimal(19, 2)) / waiting_tasks_count_delta + THEN CAST(CAST(wait_time_ms_delta AS decimal(19, 2)) / waiting_tasks_count_delta AS decimal(18, 4)) ELSE 0 END FROM collect.wait_stats WHERE wait_type IN (N'THREADPOOL', N'RESOURCE_SEMAPHORE', N'RESOURCE_SEMAPHORE_QUERY_COMPILE')