using SQLInsight.Repository;
namespace SQLInsight.Reports;
///
/// Everything the repository holds about one instance in one time window, compared with the same hours of the
/// previous weeks: the Statspack-style "what happened between 03 and 04". Used by the web page and the PDF.
///
public sealed record TimeWindowAnalysis
{
public required InstanceOverview Instance { get; init; }
public required DateTimeOffset From { get; init; }
public required DateTimeOffset To { get; init; }
public required WindowResolution Resolution { get; init; }
public required int RawDays { get; init; }
public required IReadOnlyList Metrics { get; init; }
public required IReadOnlyList Waits { get; init; }
public required IReadOnlyList Files { get; init; }
public required IReadOnlyList Blocking { get; init; }
public required IReadOnlyList Deadlocks { get; init; }
public required IReadOnlyList Findings { get; init; }
public required TopQueryOrder QueryOrder { get; init; }
/// The heaviest statements. Their text is customer data: only for the local UI and PDF.
public required IReadOnlyList TopQueries { get; init; }
public bool HasData => Metrics.Any(m => m.Points.Count > 0) || Waits.Count > 0 || Files.Count > 0 || Blocking.Count > 0
|| Deadlocks.Count > 0 || TopQueries.Count > 0;
/// The metrics shown, in order: key, label, unit, decimals, lower-is-worse, fixed maximum.
public static readonly IReadOnlyList<(string Metric, string Label, string Unit, int Decimals, bool LowerIsWorse, double? Max)> MetricDefinitions =
[
(Repository.Metrics.CpuSqlServer, "SQL Server CPU", " %", 0, false, 100),
(Repository.Metrics.CpuOther, "CPU brugt af andre processer", " %", 0, false, 100),
(Repository.Metrics.BatchRequestsPerSecond, "Batch requests pr. sekund", "", 1, false, null),
(Repository.Metrics.CompilationsPerSecond, "SQL compilations pr. sekund", "", 1, false, null),
(Repository.Metrics.PageLifeExpectancy, "Page life expectancy (s)", "", 0, true, null),
(Repository.Metrics.UserConnections, "Brugerforbindelser", "", 0, false, null),
(Repository.Metrics.MemoryGrantsPending, "Memory grants pending", "", 0, false, null),
(Repository.Metrics.ProcessesBlocked, "Blokerede processer", "", 0, false, null),
];
public static async Task LoadAsync(
InsightRepository repository, InstanceOverview instance, DateTimeOffset from, DateTimeOffset to, DateTimeOffset now,
int rawDays = 14, TopQueryOrder order = TopQueryOrder.Cpu, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(repository);
ArgumentNullException.ThrowIfNull(instance);
var id = instance.InstanceId;
var resolution = TimeWindow.ChooseResolution(from, now, rawDays);
var weeks = Enumerable.Range(1, WindowBaseline.Weeks).Select(w => TimeSpan.FromDays(7 * w)).ToList();
var metrics = new List();
foreach (var (metric, label, unit, decimals, lowerIsWorse, max) in MetricDefinitions)
{
var points = resolution == WindowResolution.Raw
? await repository.GetMetricSamplesAsync(id, metric, from, to, cancellationToken).ConfigureAwait(false)
: await repository.GetMetricHourlyAsync(id, metric, from, to, cancellationToken).ConfigureAwait(false);
var earlier = new List>();
foreach (var back in weeks)
{
earlier.Add(await repository.GetMetricHourlyAsync(id, metric, from - back, to - back, cancellationToken).ConfigureAwait(false));
}
metrics.Add(new MetricComparison(metric, label, unit, decimals, lowerIsWorse, max, points, WindowBaseline.NormalAverage(earlier)));
}
IReadOnlyList windowWaits;
double minutes;
if (resolution == WindowResolution.Raw)
{
windowWaits = await repository.GetWaitsAsync(id, from, to, cancellationToken).ConfigureAwait(false);
minutes = (to - from).TotalMinutes;
}
else
{
var hourly = await repository.GetWaitsHourlyAsync(id, from, to, cancellationToken).ConfigureAwait(false);
windowWaits = hourly.Select(h => new WaitTotal(h.WaitType, h.WaitingTasks, h.WaitTimeMs, 0)).ToList();
minutes = hourly.Count == 0 ? (to - from).TotalMinutes : hourly[0].Hours * 60.0;
}
var earlierWaits = new List>();
foreach (var back in weeks)
{
earlierWaits.Add(await repository.GetWaitsHourlyAsync(id, from - back, to - back, cancellationToken).ConfigureAwait(false));
}
return new TimeWindowAnalysis
{
Instance = instance,
From = from,
To = to,
Resolution = resolution,
RawDays = rawDays,
Metrics = metrics,
Waits = WindowBaseline.CompareWaits(windowWaits, minutes, earlierWaits),
Files = await repository.GetFileLatencyAsync(id, from, to, cancellationToken).ConfigureAwait(false),
Blocking = await repository.GetBlockingAsync(id, from, to, cancellationToken).ConfigureAwait(false),
Deadlocks = await repository.GetDeadlocksAsync(id, from, to, cancellationToken).ConfigureAwait(false),
Findings = await repository.GetFindingsActiveDuringAsync(id, from, to, cancellationToken).ConfigureAwait(false),
QueryOrder = order,
TopQueries = await repository.GetTopQueriesAsync(id, from, to, order, 25, cancellationToken).ConfigureAwait(false),
};
}
}