using SQLInsight.Core.Servers;
using SQLInsight.Core.SyntheticChecks;
using SQLInsight.Notifications;
using SQLInsight.Repository;
using SQLInsight.Rules.Servers;
using SQLInsight.Servers;
namespace SQLInsight.Agent.Servers;
/// Windows servers (SQLInsight:Servers): how and how often to collect, how long to keep it, and the rules' thresholds.
public sealed class ServerOptions : ServerRuleSettings
{
public const string SectionName = "SQLInsight:Servers";
public static readonly TimeSpan MinCycleTimeout = TimeSpan.FromMinutes(1);
public static readonly TimeSpan MaxCycleTimeout = TimeSpan.FromMinutes(30);
/// Look at Windows servers at all.
public bool Enabled { get; set; } = true;
/// The computers behind the monitored SQL Server instances are Windows servers too, without being added.
public bool IncludeSqlHosts { get; set; } = true;
/// How many servers are read at the same time (1 to 32).
public int MaxParallelServers { get; set; } = 4;
/// Auto (WS-Man, then DCOM), WSMan or DCOM.
public string Protocol { get; set; } = "Auto";
/// How long one CIM operation may take (5 seconds to 5 minutes).
public TimeSpan OperationTimeout { get; set; } = TimeSpan.FromSeconds(30);
///
/// How long one server's round may take at most (1 to 30 minutes, at least twice ): what is
/// not read by then is "ikke målbar" until the next round, so a server that hangs never holds up the others.
///
public TimeSpan CycleTimeout { get; set; } = TimeSpan.FromMinutes(5);
/// Read event logs with the Event Log service's own protocol (filters on the server); off: CIM only.
public bool EventLogRpc { get; set; } = true;
/// Overrides of the collection intervals by area: performance, disks, services, events, tasks.
public IDictionary Intervals { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase);
/// Per-minute CPU and memory samples (at least 1 day).
public int SampleDays { get; set; } = 14;
/// Free space per volume: at least the forecast's history.
public int DiskHistoryDays { get; set; } = 35;
/// Grouped Windows events (at least 1 day).
public int EventDays { get; set; } = 90;
/// The share of the scheduled tasks' run information may take; the rest is read next time.
public TimeSpan TaskBudget => CycleTimeout / 3;
public TimeSpan IntervalOf(string area) =>
Intervals.TryGetValue(area, out var interval) && interval >= TimeSpan.FromSeconds(30) ? interval : ServerArea.DefaultInterval(area);
public ServerConnectionOptions Connection => new() { Protocol = Protocol, OperationTimeout = OperationTimeout, EventLogRpc = EventLogRpc };
///
/// Keeps the settings within what works: a timeout of 0 or hours, retention of 0 days (maintenance would delete
/// everything) or unbounded parallelism are moved to the nearest sensible value. Applied when the options are bound.
///
public ServerOptions Normalize()
{
MaxParallelServers = Math.Clamp(MaxParallelServers, 1, 32);
OperationTimeout = ServerConnectionOptions.BoundTimeout(OperationTimeout);
var cycle = CycleTimeout < OperationTimeout * 2 ? OperationTimeout * 2 : CycleTimeout;
CycleTimeout = cycle < MinCycleTimeout ? MinCycleTimeout : cycle > MaxCycleTimeout ? MaxCycleTimeout : cycle;
SampleDays = Math.Max(1, SampleDays);
DiskHistoryDays = Math.Max(1, DiskHistoryDays);
EventDays = Math.Max(1, EventDays);
Disks.ForecastHistoryDays = Math.Clamp(Disks.ForecastHistoryDays, 1, 365);
EventLog.MaxEventsPerCollection = Math.Clamp(EventLog.MaxEventsPerCollection, 10, 10000);
Tasks.MaxWatched = Math.Clamp(Tasks.MaxWatched, 1, 200);
return this;
}
}
/// Synthetic checks (SQLInsight:SyntheticChecks): how many run at once, how long results are kept, and the rules' thresholds.
public sealed class SyntheticCheckOptions : SyntheticCheckRuleSettings
{
public const string SectionName = "SQLInsight:SyntheticChecks";
/// The most recent results the rules look at: enough for "fejlet i træk", never unbounded.
public const int MaxRecentResults = 100;
public bool Enabled { get; set; } = true;
/// How many checks run at the same time (1 to 32).
public int MaxParallelChecks { get; set; } = 4;
/// Every run's result (at least 1 day).
public int ResultDays { get; set; } = 14;
/// Where the Dynamics AX .NET Business Connector is, when not where the AX installer puts it.
public string? BusinessConnectorPath { get; set; }
/// How many recent results the rules are given: more than they need to count failures or slow runs in a row.
public int RecentResults => Math.Clamp(Math.Max(10, Math.Max(FailuresBeforeFinding, SlowRunsBeforeFinding)) + 1, 11, MaxRecentResults);
/// Keeps the settings within what works (see ). Applied when the options are bound.
public SyntheticCheckOptions Normalize()
{
MaxParallelChecks = Math.Clamp(MaxParallelChecks, 1, 32);
ResultDays = Math.Max(1, ResultDays);
FailuresBeforeFinding = Math.Clamp(FailuresBeforeFinding, 1, MaxRecentResults - 1);
SlowRunsBeforeFinding = Math.Clamp(SlowRunsBeforeFinding, 1, MaxRecentResults - 1);
return this;
}
/// A check's interval as the agent runs it: never more often than .
public static TimeSpan IntervalOf(int intervalSeconds) =>
TimeSpan.FromSeconds(Math.Clamp(intervalSeconds, SyntheticCheckLimits.MinIntervalSeconds, SyntheticCheckLimits.MaxIntervalSeconds));
}
/// Notifications about Windows servers and synthetic checks, with links to their pages in the web UI.
internal static class ServerNotifications
{
public static IEnumerable For(string name, FindingChanges changes, Uri? webUrl, DateTimeOffset now)
{
foreach (var (id, finding) in changes.Opened)
{
yield return NotificationFactory.Opened(name, id, finding, webUrl, now) with { Url = Link(webUrl, id) };
}
foreach (var resolved in changes.Resolved)
{
yield return NotificationFactory.Resolved(name, resolved.FindingId, resolved.RuleId, resolved.Title, (Core.Findings.Severity)resolved.Severity, webUrl, now)
with { Url = Link(webUrl, resolved.FindingId) };
}
}
public static Uri? Link(Uri? webUrl, long findingId) => webUrl is null ? null : new Uri(webUrl, $"servere/fund/{findingId}");
public static Uri? ServerPage(Uri? webUrl, int serverId) => webUrl is null ? null : new Uri(webUrl, $"servere/{serverId}");
}