namespace SQLInsight.Core.Data; /// A session using tempdb (user objects plus internal objects: sorts, hash tables, spools). Login and program are sensitive. /// When the session logged in; with it identifies the session across samples. public sealed record TempdbSession(int SessionId, DateTimeOffset Since, double UsedMegabytes, string? LoginName, string? ProgramName, string? HostName, string? Status) { public string Key => $"{SessionId}|{Since.UtcTicks}"; } /// The oldest open snapshot transaction: it keeps every row version made since it began. public sealed record SnapshotTransaction(int SessionId, long ElapsedSeconds, string? LoginName, string? ProgramName); /// Version store use per database (from sys.dm_tran_top_version_generators). public sealed record VersionGenerator(string DatabaseName, double Megabytes); /// tempdb space and the version store, every 5 minutes. public sealed record TempdbSnapshot { public required DateTimeOffset CapturedAt { get; init; } /// The size of tempdb's data files now. public double SizeMegabytes { get; init; } public double FreeMegabytes { get; init; } public double VersionStoreMegabytes { get; init; } public double UserObjectsMegabytes { get; init; } public double InternalObjectsMegabytes { get; init; } /// Whether a data file can still grow (autogrowth on and not at its maximum size). public bool CanGrow { get; init; } = true; public IReadOnlyList TopSessions { get; init; } = []; public SnapshotTransaction? OldestSnapshotTransaction { get; init; } public IReadOnlyList VersionGenerators { get; init; } = []; /// Version store sizes of the latest samples (MB), oldest first: the trend. public IReadOnlyList VersionStoreHistory { get; init; } = []; /// Since when each session has been above , by . public IReadOnlyDictionary HeavySessionsSince { get; init; } = new Dictionary(); /// The version store above this is a finding (SQLInsight:Tempdb:VersionStoreMegabytes). public int VersionStoreThresholdMegabytes { get; init; } = 1024; /// A session using more tempdb than this for is a finding. public int SessionThresholdMegabytes { get; init; } = 1024; public double FreePercent => SizeMegabytes <= 0 ? 100 : 100 * FreeMegabytes / SizeMegabytes; } public static class TempdbAnalysis { public const int HistoryLength = 12; public const int HeavySessionMinutes = 15; /// The version store has grown in each of the last six samples, by a quarter GB in all. public static bool IsGrowing(IReadOnlyList history) { ArgumentNullException.ThrowIfNull(history); if (history.Count < 6) { return false; } var last = history.Skip(history.Count - 6).ToList(); return last.Zip(last.Skip(1)).All(p => p.Second > p.First) && last[^1] - last[0] >= 256; } /// Carries the trend and the heavy sessions over from the previous sample. public static TempdbSnapshot Advance(TempdbSnapshot? previous, TempdbSnapshot current) { ArgumentNullException.ThrowIfNull(current); var history = (previous?.VersionStoreHistory ?? []).Append(current.VersionStoreMegabytes).TakeLast(HistoryLength).ToList(); var heavy = new Dictionary(StringComparer.Ordinal); foreach (var s in current.TopSessions.Where(s => s.UsedMegabytes >= current.SessionThresholdMegabytes)) { heavy[s.Key] = previous?.HeavySessionsSince.TryGetValue(s.Key, out var since) == true ? since : current.CapturedAt; } return current with { VersionStoreHistory = history, HeavySessionsSince = heavy }; } }