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 };
}
}