using SQLInsight.Core.Data;
using SQLInsight.Core.Instances;
namespace SQLInsight.Collectors.Performance;
///
/// 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+.
///
public sealed class WaitSamplingCollector : Collector
{
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))
""";
/// The statement key as top SQL makes it on 2008+: query_hash.
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;
/// SQL Server 2005 has no query_hash: the batch (sql_handle) and the statement's offsets, as top SQL.
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 RequiredCapabilities => [Capability.DynamicManagementViews];
public override bool IsSampling => true;
protected override async Task 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);
}
}