using SQLInsight.Connectivity; using SQLInsight.Repository; namespace SQLInsight.Agent; public sealed class AgentOptions { public const string SectionName = "SQLInsight"; /// How often the scheduler checks whether collectors are due. public TimeSpan Tick { get; set; } = TimeSpan.FromSeconds(15); /// How many instances are checked at the same time. public int MaxParallelInstances { get; set; } = 4; /// Overrides of collector intervals by collector name, e.g. "blocking": "00:00:30". public IDictionary CollectorIntervals { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase); public RepositoryOptions Repository { get; set; } = new(); public RetentionOptions Retention { get; set; } = new(); public PrivilegeOptions Privileges { get; set; } = new(); public TopSqlOptions TopSql { get; set; } = new(); public ErrorLogOptions ErrorLog { get; set; } = new(); public ActivityOptions Activity { get; set; } = new(); public TempdbOptions Tempdb { get; set; } = new(); public DynamicsAxOptions DynamicsAx { get; set; } = new(); public IList Instances { get; } = []; } public sealed class RepositoryOptions { /// /// Connection string to the SQLInsight repository database. Use a SQL Server 2016+ instance other than the /// ones being monitored. /// public string? ConnectionString { get; set; } } public sealed class MonitoredInstance { /// Stable name shown in the UI and used as the key in the repository and SQLInsight Hub. public string Name { get; set; } = string.Empty; public string Server { get; set; } = string.Empty; public AuthenticationMode Authentication { get; set; } = AuthenticationMode.Windows; public string? UserName { get; set; } public string? Password { get; set; } public ConnectionProvider Provider { get; set; } = ConnectionProvider.Auto; public EncryptionMode Encryption { get; set; } = EncryptionMode.Mandatory; public bool TrustServerCertificate { get; set; } public string OdbcDriver { get; set; } = "ODBC Driver 18 for SQL Server"; /// Entra tenant id, for EntraServicePrincipal. public string? TenantId { get; set; } public ConnectionTarget ToTarget() => new() { Server = Server, Authentication = Authentication, UserName = UserName, Password = Password, Provider = Provider, Encryption = Encryption, TrustServerCertificate = TrustServerCertificate, OdbcDriver = OdbcDriver, TenantId = TenantId, }; } /// The privilege audit (sysadmin and the other elevated server rights). public sealed class PrivilegeOptions { /// More people and accounts with sysadmin than this is a finding (SQLI-SEC-002). public int MaxSysadminAccounts { get; set; } = 3; /// Unfold Windows groups with server rights to the people in them, by asking Active Directory (Windows only). public bool ExpandWindowsGroups { get; set; } = true; } /// Top SQL (the heaviest statements). public sealed class TopSqlOptions { /// /// A statement whose CPU time is more than this share of the time it has been in the plan cache runs almost /// constantly (SQLI-PERF-…); 100 means one CPU core all the time. /// public double ActivityThresholdPercent { get; set; } = 50; } /// The SQL Server error log. public sealed class ErrorLogOptions { /// Failed logins within one collection (5 minutes) before it is a finding (SQLI-LOG-004). public int LoginFailureBurst { get; set; } = 20; } /// Open transactions and long-running statements. public sealed class ActivityOptions { /// A transaction open longer than this (minutes) is a finding (SQLI-TRAN-001); six times as long is High. public int TransactionMinutes { get; set; } = 10; /// A transaction holding more transaction log than this (MB) is a finding (SQLI-TRAN-002). public int TransactionLogMegabytes { get; set; } = 1024; /// A user statement running longer than this (minutes) is a finding (SQLI-REQ-001). public int RequestMinutes { get; set; } = 15; } /// tempdb and the version store. public sealed class TempdbOptions { /// The version store above this (MB) is a finding (SQLI-TEMP-001). public int VersionStoreMegabytes { get; set; } = 1024; /// A session using more tempdb than this (MB) for 15 minutes is a finding (SQLI-TEMP-003). public int SessionMegabytes { get; set; } = 1024; } /// /// The Dynamics AX checks (SQLInsight:DynamicsAx). They only run in databases recognised as AX and chosen for monitoring; /// the data checks need the optional read access (db/permissions/grant-dynamics-ax-read.sql). /// public sealed class DynamicsAxOptions { /// A waiting batch job this many minutes past its planned start is late (SQLI-AX-003). public int BatchLateMinutes { get; set; } = 30; /// Late by this many hours: in practice never started (High). public int BatchNeverStartedHours { get; set; } = 24; /// A batch job executing this many hours is stuck (SQLI-AX-004). public int BatchStuckHours { get; set; } = 6; /// Running this many times longer than its own 90th percentile is unusually long (SQLI-AX-002). public double BatchLongRunningFactor { get; set; } = 2.0; /// …but never for runs shorter than this (minutes). public int BatchLongRunningMinimumMinutes { get; set; } = 10; /// Earlier runs needed before a job is compared with its own history. public int BatchHistoryMinimumRuns { get; set; } = 5; /// A run that failed within this many hours is reported (SQLI-AX-001). public int BatchFailedLookbackHours { get; set; } = 24; /// A number sequence used more than this (percent) is a finding (SQLI-AX-005). public double NumberSequenceWarnPercent { get; set; } = 80; /// …and High from this. public double NumberSequenceCriticalPercent { get; set; } = 95; /// Forecast to run out within this many days is a finding (number sequences and 64-bit RecId). public int ForecastWarnDays { get; set; } = 90; /// …and High within this many days. public int ForecastCriticalDays { get; set; } = 30; /// AX 3.0's 32-bit RecId: a finding from this share of the int range (SQLI-AX-009). public double RecId32WarnPercent { get; set; } = 50; /// …and Critical from this. public double RecId32CriticalPercent { get; set; } = 75; /// AX 3.0: forecast to run out within this many days is a finding, Critical within half. public int RecId32ForecastDays { get; set; } = 730; /// 64-bit RecId (or a lowered MAXVAL): a finding from this share. public double RecId64WarnPercent { get; set; } = 80; /// An AX session blocking others this many seconds is reported with its AX user (SQLI-AX-010). public int BlockingSeconds { get; set; } = 30; /// How many days back the forecasts look. public int ForecastBasisDays { get; set; } = 30; /// The AOS's time zone (Windows or IANA id) for Axapta 3.0/4.0's local times; empty: the agent's own zone when it matches the SQL Server. public string? LocalTimeZone { get; set; } public Core.DynamicsAx.AxThresholds ToThresholds() => new() { BatchLateMinutes = Math.Max(1, BatchLateMinutes), BatchNeverStartedHours = Math.Max(1, BatchNeverStartedHours), BatchStuckHours = Math.Max(1, BatchStuckHours), BatchLongRunningFactor = Math.Max(1.1, BatchLongRunningFactor), BatchLongRunningMinimumMinutes = Math.Max(1, BatchLongRunningMinimumMinutes), BatchHistoryMinimumRuns = Math.Clamp(BatchHistoryMinimumRuns, 1, Core.DynamicsAx.AxBatchHistory.RunsKept), BatchFailedLookbackHours = Math.Max(1, BatchFailedLookbackHours), NumberSequenceWarnPercent = Math.Clamp(NumberSequenceWarnPercent, 1, 100), NumberSequenceCriticalPercent = Math.Clamp(NumberSequenceCriticalPercent, 1, 100), ForecastWarnDays = Math.Max(1, ForecastWarnDays), ForecastCriticalDays = Math.Max(1, ForecastCriticalDays), RecId32WarnPercent = Math.Clamp(RecId32WarnPercent, 1, 100), RecId32CriticalPercent = Math.Clamp(RecId32CriticalPercent, 1, 100), RecId32ForecastDays = Math.Max(1, RecId32ForecastDays), RecId64WarnPercent = Math.Clamp(RecId64WarnPercent, 1, 100), BlockingSeconds = Math.Max(1, BlockingSeconds), ForecastBasisDays = Math.Clamp(ForecastBasisDays, 2, 365), LocalTimeZone = string.IsNullOrWhiteSpace(LocalTimeZone) ? null : LocalTimeZone.Trim(), }; }