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(),
};
}