sqlinsight-pro/src/SQLInsight.Collectors/Performance/WaitSamplingCollector.cs
Søren Eggert Lundsteen Olsen 20671bfb06 Wait type drill-down with active session sampling
A new light collector samples sys.dm_exec_requests every 15 seconds:
waiting user sessions (not the agent's own) by wait type, database and
statement (same key as top SQL), with blocking. Sampling-only cycles
reuse the last probe and skip rule evaluation. Stored in
insight.ActiveWaitSamples (migration 0010) for RawDays.

Wait types on the instance page and in the analysis link to
/instanser/{navn}/ventetyper/{type}: Danish explanation from a new
knowledge base (shared with the SQLI-WAIT rules, with Dynamics AX notes),
trend over time, the statements that waited with share and estimated
wait, a link to the full text (/instanser/{navn}/sql) and, for lock
waits, to blocking in the same window. SQL Server 2005 compatible.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 08:50:31 +02:00

54 lines
2.9 KiB
C#

using SQLInsight.Core.Data;
using SQLInsight.Core.Instances;
namespace SQLInsight.Collectors.Performance;
/// <summary>
/// Active session sampling: every 15 seconds, what each waiting user request waits on, in which database and for
/// which statement (the same key as top SQL). Sums of these samples show which statements a wait type comes from.
/// The agent's own sessions and system sessions are left out. One light query on sys.dm_exec_requests; SQL Server 2005+.
/// </summary>
public sealed class WaitSamplingCollector : Collector<WaitSampleSnapshot>
{
private const string From = """
FROM sys.dm_exec_requests r
JOIN sys.dm_exec_sessions s ON s.session_id = r.session_id
OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) st
WHERE s.is_user_process = 1 AND r.session_id <> @@SPID AND r.wait_type IS NOT NULL
AND (s.program_name IS NULL OR s.program_name NOT LIKE N'SQLInsight Pro%')
""";
private const string Columns = """
r.session_id, r.wait_type, r.wait_time, DB_NAME(r.database_id), r.blocking_session_id,
""";
private const string Text = """
CAST(SUBSTRING(st.text, r.statement_start_offset / 2 + 1,
CASE WHEN r.statement_end_offset = -1 THEN 4000 ELSE (r.statement_end_offset - r.statement_start_offset) / 2 + 1 END) AS nvarchar(4000))
""";
/// <summary>The statement key as top SQL makes it on 2008+: query_hash.</summary>
internal const string Sql2008 = "SELECT " + Columns + """
CASE WHEN r.query_hash IS NULL OR r.query_hash = 0x0000000000000000 THEN NULL ELSE CONVERT(varchar(20), r.query_hash, 1) END,
""" + "\n" + Text + "\n" + From;
/// <summary>SQL Server 2005 has no query_hash: the batch (sql_handle) and the statement's offsets, as top SQL.</summary>
internal const string Sql2005 = "SELECT " + Columns + """
CASE WHEN r.sql_handle IS NULL THEN NULL
ELSE sys.fn_varbintohexstr(r.sql_handle) + ':' + CAST(r.statement_start_offset AS varchar(12)) + ':' + CAST(r.statement_end_offset AS varchar(12)) END,
""" + "\n" + Text + "\n" + From;
public override string Name => "wait-sampling";
public override TimeSpan DefaultInterval => WaitSampling.Interval;
public override IReadOnlyCollection<Capability> RequiredCapabilities => [Capability.DynamicManagementViews];
public override bool IsSampling => true;
protected override async Task<WaitSampleSnapshot> CollectAsync(CollectorContext context, WaitSampleSnapshot? previous)
{
var sql = context.Instance.Version.Major >= 10 ? Sql2008 : Sql2005;
var requests = await Db.QueryAsync(context.Connection, sql, r => new WaitingRequest(
r.Int(0), r.String(1), r.Long(2), r.NullableString(3), r.NullableString(5), r.NullableString(6), r.NullableInt(4) ?? 0),
context.CancellationToken).ConfigureAwait(false);
return WaitSampling.Aggregate(requests, context.Now);
}
}