Some checks are pending
CI / Integration (mcr.microsoft.com/mssql/server:2017-latest) (push) Blocked by required conditions
CI / Build og unit tests (push) Waiting to run
CI / Integration (mcr.microsoft.com/mssql/server:2019-latest) (push) Blocked by required conditions
CI / Integration (mcr.microsoft.com/mssql/server:2022-latest) (push) Blocked by required conditions
CI / Integration (mcr.microsoft.com/mssql/server:2025-latest) (push) Blocked by required conditions
Baselines per hour of the week (with neighbouring hours) from four weeks of hourly aggregates, computed in C# (independent of DATEFIRST) and refreshed by the agent each hour. SQLI-ANOM-001 compares current load with what is normal at this time: CPU far above normal, batch requests far above or collapsed (application down?), page life expectancy collapse. Not measurable until enough history exists. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E3EExwndy8Z22EK2Qpt6BK
70 lines
2.7 KiB
C#
70 lines
2.7 KiB
C#
using SQLInsight.Core.Data;
|
||
using SQLInsight.Core.Findings;
|
||
using SQLInsight.Rules.Engine;
|
||
using SQLInsight.Rules.Performance;
|
||
using static SQLInsight.Rules.Tests.TestInstances;
|
||
|
||
namespace SQLInsight.Rules.Tests;
|
||
|
||
public class AnomalyRuleTests
|
||
{
|
||
private static RuleResult Run(RuleContext context) => Assert.Single(new RuleEngine([new AnomalyRule()]).Evaluate(context));
|
||
|
||
private static BaselineSnapshot Baseline(params (string Metric, double Mean, double Stdev, int Samples)[] metrics) => new()
|
||
{
|
||
CapturedAt = Now,
|
||
HourOfWeek = 37,
|
||
Metrics = metrics.ToDictionary(m => m.Metric, m => new MetricBaseline(m.Mean, m.Stdev, m.Samples)),
|
||
};
|
||
|
||
private static CpuUtilization Cpu(int percent) => new()
|
||
{
|
||
CapturedAt = Now,
|
||
Samples = Enumerable.Range(0, 15).Select(i => new CpuSample(Now.AddMinutes(i - 15), percent, 100 - percent)).ToList(),
|
||
};
|
||
|
||
private static CumulativeSample<PerformanceCounterSnapshot, PerformanceCounterRates> Counters(double batches, long ple) => new(
|
||
new PerformanceCounterSnapshot { CapturedAt = Now, SqlServerStartTime = Now.UtcDateTime.AddDays(-1), PageLifeExpectancySeconds = ple },
|
||
new PerformanceCounterRates { From = Now.AddMinutes(-5), To = Now, BatchRequestsPerSecond = batches });
|
||
|
||
[Fact]
|
||
public void Too_little_history_is_not_measurable()
|
||
{
|
||
var result = Run(With(Baseline((AnomalyRule.CpuMetric, 20, 5, 3)), Cpu(90)));
|
||
|
||
Assert.Equal(RuleOutcome.NotMeasurable, result.Outcome);
|
||
Assert.Contains("uger", result.Reason, StringComparison.Ordinal);
|
||
}
|
||
|
||
[Fact]
|
||
public void Cpu_far_above_normal_for_this_hour()
|
||
{
|
||
var finding = Assert.Single(Run(With(Baseline((AnomalyRule.CpuMetric, 20, 5, 9)), Cpu(75))).Findings);
|
||
|
||
Assert.Equal("anomaly: cpu", finding.Subject);
|
||
Assert.Contains("tirsdag kl. 13–14 UTC", finding.Evidence[1].Value, StringComparison.Ordinal);
|
||
}
|
||
|
||
[Fact]
|
||
public void High_cpu_that_is_normal_for_this_hour_passes()
|
||
{
|
||
Assert.Equal(RuleOutcome.Passed, Run(With(Baseline((AnomalyRule.CpuMetric, 80, 8, 9)), Cpu(88))).Outcome);
|
||
}
|
||
|
||
[Fact]
|
||
public void Traffic_drop_suggests_an_application_is_down()
|
||
{
|
||
var finding = Assert.Single(Run(With(Baseline((AnomalyRule.BatchMetric, 800, 100, 9)), Counters(batches: 40, ple: 5000))).Findings);
|
||
|
||
Assert.Equal(Severity.Medium, finding.Severity);
|
||
Assert.Contains("færre", finding.Explanation, StringComparison.Ordinal);
|
||
}
|
||
|
||
[Fact]
|
||
public void Ple_collapse()
|
||
{
|
||
var finding = Assert.Single(Run(With(Baseline((AnomalyRule.PleMetric, 4000, 500, 9)), Counters(batches: 0, ple: 300))).Findings);
|
||
|
||
Assert.Equal("anomaly: ple", finding.Subject);
|
||
}
|
||
}
|