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