One place recognises AX (version 3.0/4.0/2009/2012/2012 R2/F&O, schema owner,
model store) from metadata only. Per-version adapters hold all table/column
knowledge, check every column in sys.columns before use and degrade to
'ikke målbar' with the exact column or the exact GRANT text. Only AX 3.0 is
verified against a real customer schema; 2009/2012 are written from
Microsoft's documented schema (docs/dynamics-ax/*.documented.tsv, replaced
by a real export when one is dropped in). grant-dynamics-ax-read.sql is
version- and schema-aware (bmssa) and covers exactly the adapters' tables.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
With ALTER TRACE the error log collector read every default trace file
(up to 5 x 20 MB) every 5 minutes. While the current file is the one read
last time, everything since then is in it, so only that file is read;
after a rollover (and the first time) all files are read once as before.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
sys.dm_tran_top_version_generators reads the whole version store. It ran
every 5 minutes while the version store was over half the threshold,
i.e. exactly when it is large and expensive to scan, and a timeout failed
the whole tempdb collection. It now runs at most once per hour (the
previous answer is kept in between, with its time) and a failed scan
keeps the previous answer.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
sys.dm_db_index_usage_stats also starts over when a database is taken
offline, closed by AUTO_CLOSE or (2012/2014) an index is rebuilt. The
delta was then clamped to 0, so the usage since the reset was lost from
the daily history and the tracked totals. A lower counter now counts as
new usage. IndexInfo.Key also used the literal text "001f" as its
separator instead of the unit separator character.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
When the distribution database could not be read, SQL Server's message
(which names the agent's login) was appended to Unavailable. That text is
the rules' "not measurable" reason and is returned unredacted by
/rule-states and /replication. It is now UnavailableDetail: shown on the
local instance page and [udeladt] in the API unless
ApiIncludesSensitiveEvidence. The agent list is also capped (TOP 500).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The error log page called the default trace "the whole error log" and
said it has every line, and the ring buffer source told SQLI-LOG-002/005
to grant ALTER TRACE, which does not make 833 or stack dumps visible
(they are only in the error log itself). The rules now say that it takes
securityadmin, why SQLInsight does not grant it, and the exact statement.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The availability group collector also reads sys.dm_hadr_cluster_members
(empty or unavailable without a WSFC), and SQLI-AG-003 (High) reports a
node or witness that is not up, with how many members are still up.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The index collector now sums index reads and writes between collections
across restarts of SQL Server (and of the agent, by seeding from the
stored snapshot) and keeps them per day in insight.IndexUsageDaily
(migration 0015, HourlyDays). SQLI-IDX-002 judges an index once it has
been followed for 7 days instead of needing 7 days of uptime.
GET /api/v1/instances/{name}/index-usage lists usage per index.
SQLI-STAT-001 also reports statistics that were never updated on tables
with rows; statistic rows now come from the table's heap or clustered
index, as a never-updated statistic has no row count of its own.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
On a distributor, a 15-minute collector reads each distribution agent's
latest run (status, latency, message) and replication errors the last
day from the distribution database. That needs SELECT on
MSdistribution_agents, MSdistribution_history and MSrepl_errors, which
the agent does not have by default (never the replicated commands);
without it SQLI-REPL-001 (agent failed/retrying, High) and SQLI-REPL-002
(latency > 5 min, Medium) are "not measurable" with the exact grant, and
"not relevant" elsewhere. Shown on the instance page and in
GET /api/v1/instances/{name}/replication. Tested against a real
distributor set up in the SQL 2022 container.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A five-minute collector reads tempdb space (free, version store, user and
internal objects, whether files can grow), the 'Version Store Size (KB)'
counter, the sessions using most tempdb, the oldest snapshot transaction
and, only when the version store is large, the databases generating
versions. Trend and heavy sessions survive restarts; used space and the
version store become metrics with explanation and assessment.
Rules SQLI-TEMP-001 (version store > 1 GB or steadily growing),
SQLI-TEMP-002 (nearly full and cannot grow) and SQLI-TEMP-003 (session
above 1 GB for 15 minutes), configurable under SQLInsight:Tempdb.
"tempdb lige nu" on the instance page; GET /api/v1/instances/{name}/tempdb.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A one-minute collector reads open user transactions (age, log bytes used
and reserved, database, session, login, program, statement) and user
requests running longer than the threshold, leaving out the agent's own
and system sessions, BACKUP/RESTORE/DBCC (long operations) and requests
waiting on purpose. Transactions open a minute or holding 100 MB are kept
in insight.LongTransactions (migration 0014, RawDays).
Rules SQLI-TRAN-001 (open > 10 min, High at 6x), SQLI-TRAN-002 (> 1 GB
log) and SQLI-REQ-001 (statement > 15 min), configurable under
SQLInsight:Activity. Shown "right now" on the instance page and in the
time window analysis and PDF; GET /api/v1/instances/{name}/activity and
the analysis JSON leave logins and statements out.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A five-minute collector reads important error log entries from the best
source the agent's rights allow: the error log itself (xp_readerrorlog,
only with securityadmin), the default trace's ErrorLog and Audit Login
Failed events (optional ALTER TRACE), or the exception and connectivity
ring buffers (VIEW SERVER STATE: numbers and severity, failed logins).
Repetitions are summed per error; the watermark is the newest entry
read, because the default trace is buffered. Stored in
insight.ErrorLogEvents (migration 0013, EventDays), resumed after restart.
Rules SQLI-LOG-001..006: corruption 823/824/825, I/O over 15 s (833),
severe errors (17+), login failure bursts (configurable), stack dumps and
AG errors; rules needing the log text are "not measurable" with the
right to grant. Page /instanser/{navn}/fejllog (menu Overvågning) and
GET /api/v1/instances/{name}/error-log with messages redacted.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Long operations: a one-minute collector reads operations with progress
from sys.dm_exec_requests (BACKUP, RESTORE, DBCC, REORGANIZE, SHRINK,
ROLLBACK): command, database, start, percent, expected end, login.
Stored in insight.LongOperations (migration 0011, RawDays), shown on
the instance page, in the time window analysis and its PDF. The last
10 durations per command and database survive restarts; SQLI-OPS-001
flags a backup/restore running over twice the median.
- Full-text community rules COM-FT-001..005 (metadata only, 2005 syntax):
disk-full or paused/throttled catalogs, stale manual/off change
tracking indexes and disabled indexes.
- Statement activity (CPU time / time in plan cache): column in top SQL
and SQLI-CPU-002 when above SQLInsight:TopSql:ActivityThresholdPercent
(default 50) for at least an hour. Never clears the plan cache.
- BuiltInCollectors.Create takes a CollectorSettings record.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A new light collector samples sys.dm_exec_requests every 15 seconds:
waiting user sessions (not the agent's own) by wait type, database and
statement (same key as top SQL), with blocking. Sampling-only cycles
reuse the last probe and skip rule evaluation. Stored in
insight.ActiveWaitSamples (migration 0010) for RawDays.
Wait types on the instance page and in the analysis link to
/instanser/{navn}/ventetyper/{type}: Danish explanation from a new
knowledge base (shared with the SQLI-WAIT rules, with Dynamics AX notes),
trend over time, the statements that waited with share and estimated
wait, a link to the full text (/instanser/{navn}/sql) and, for lock
waits, to blocking in the same window. SQL Server 2005 compatible.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Collects members of the fixed server roles and CONTROL SERVER every 15
minutes with login type, disabled state and SQL login password policy.
Windows groups are unfolded recursively through Active Directory on a
Windows agent (cached, 20 s timeout, failures shown with the reason).
Changes since the previous collection are logged, and who/when is read
from the default trace (event 108) when the agent has the optional
ALTER TRACE; authors that only reach the buffered trace later are filled
in at the next collection. The first collection is the baseline.
New rules SQLI-SEC-001 (new privilege, High for sysadmin/securityadmin/
CONTROL SERVER) and SQLI-SEC-002 (more sysadmins than the limit, default
3). Names are sensitive evidence and never in titles or subjects.
Web: /instanser/{navn}/rettigheder and /rettigheder with change log by
period, alert banner on the instance page and overview, and an audit
PDF (/rapporter/rettigheder.pdf) linked from Rapporter.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- new collector instance-config: collation, edition, version, services and
accounts (sys.dm_server_services, 2008 R2 SP1+), Instant File
Initialization and Lock Pages in Memory (where the build has the
columns), tempdb files, default paths; plus a fingerprint of the whole
setup (sp_configure, trace flags, services, version, tempdb files)
- changes are computed against the previous collection, which the agent
restores from the repository after a restart; migration 0008 keeps them
in insight.ConfigurationChanges (as long as resolved findings)
- SQLI-CFG-013 "Konfiguration ændret": information for a day when
sp_configure, a trace flag or a service account changes (account names
are sensitive evidence)
- page /instanser/{navn}/konfiguration: instance, CPU and memory, services,
tempdb, trace flags, non-default sp_configure (default vs. in use vs.
configured) and the change log; linked from the instance page
- the management report lists configuration changes in the period
- trace flag reading shared by trace-flags and instance-config
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- file-io records each file's drive: sys.dm_os_volume_stats from 2008 R2 SP1
(new capability VolumeStats, mount points included), otherwise derived
from the path (D:\..., \server\share\, / on Linux), so 2005 works too
- migration 0007: Volume and IntervalSeconds on FileIoSamples, and
DiskHourly (rolled up by maintenance, kept HourlyDays)
- DiskActivity sums files per drive: read/write IOPS, MB/s and latency
weighted by I/O count
- instance page: "Diskhastighed pr. drev" (table and MB/s + latency charts);
time window analysis and its PDF: per drive against the same hours of the
four previous weeks
- SQLI-IO-001/002 now report per drive (one finding when the storage is
slow, the slowest files in the evidence) instead of per file
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
At a customer whose repository sits on a monitored instance, the agent
reported 12 deadlocks that were its own: two instances' monitors save
findings in parallel, one transaction each, and while insight.Findings is
small SQL Server scans it, so the two lock each other's rows.
- SaveRuleResultsAsync takes an exclusive application lock for its
transaction (the agent is the only writer; a save takes milliseconds)
and retries if chosen as deadlock victim anyway
- repository connections are named "SQLInsight Pro (repository)"
- deadlocks where every participant is SQLInsight Pro are not reported
(the read position still moves past them); any other session keeps it
The new integration test (two instances saving 25 rules x 40 rounds in
parallel) failed 2 of 2 without the fix and passed 5 of 5 with it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
New collector query-stats reads sys.dm_exec_query_stats for the statements
that ran since the previous collection, grouped by query_hash (SQL Server
2008+) or sql_handle + offsets (2005, via legacy ODBC; literals only). Only
the 50 heaviest by CPU, duration, reads and executions, text cut to 4000
characters, no plan XML. The database comes from the plan's dbid attribute,
because ad hoc batches (as Dynamics AX sends) have none in sql_text.
QueryStatsDelta diffs against the previous collection: a statement seen
before with the same plan gets the difference, a plan compiled since then
counts in full, a recompiled or evicted plan or counters that went
backwards start over (never negative), a restart gives no delta. Up to 2000
statements are remembered between collections.
Repository migration 0006: insight.QueryStatSamples (deltas, top 100 per
collection) and insight.QueryTexts (text once per statement), kept for
Retention:RawDays. The text is customer data: never stored as a JSON
snapshot and never part of hub submissions. Window queries for metrics,
waits (raw and hourly), file I/O, blocking, deadlocks, findings active in a
window and top SQL by CPU/duration/reads/executions.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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
- Availability group collector (replica and database replica states,
queues, commit times) and rules: SQLI-AG-001 unhealthy/disconnected
replica or suspended data movement (scripts HADR RESUME), SQLI-AG-002
secondary behind (estimated data loss and redo time)
- SQL Agent collector using only granted msdb tables (no EXECUTE on msdb
functions): SQLI-JOB-001 failed job with the failing step's message
(backup/CHECKDB jobs are High), SQLI-JOB-002 SQL Agent not running
- Integration tests create a real availability group (CLUSTER_TYPE NONE)
and a failing job in the test container (HADR and Agent enabled)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E3EExwndy8Z22EK2Qpt6BK
Collector (every 6 h, metadata and DMVs only, no scans): index inventory
with usage and forwarded fetches, missing-index suggestions, statistics
modifications via sysindexes.rowmodctr (needs only metadata visibility).
Rules:
- SQLI-IDX-001 missing index, evaluated against existing indexes:
extends an index with the same keys instead of adding one; notes on
write-heavy tables, many indexes, wide includes, short uptime
- SQLI-IDX-002 unused index (needs 7 days of uptime; suggests DISABLE)
- SQLI-IDX-003 duplicate or covered index (filters, uniqueness respected)
- SQLI-IDX-004 heap with forwarded records
- SQLI-STAT-001 stale statistics, SQLI-STAT-002 auto statistics off
Column lists use U+E000 as separator: FOR XML cannot serialize NCHAR(1).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E3EExwndy8Z22EK2Qpt6BK
Collector takes the 50 heaviest statements (CPU, last 24 h), fetches
statement plans as text (dm_exec_query_plan returns NULL for deeply
nested plans) and analyzes them in C#. Rules:
- SQLI-PLAN-001 implicit conversion preventing an index seek
- SQLI-PLAN-002 join without predicate (deliberate CROSS JOIN excluded)
- SQLI-PLAN-003 non-inlined scalar UDF in a heavy statement
- SQLI-PLAN-004 spills to tempdb, SQLI-PLAN-005 oversized memory grants
(2016 SP2+ / 2017 CU3+ columns, new capability)
Verified against real plans in SQL Server.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E3EExwndy8Z22EK2Qpt6BK
Collector reads the 100 heaviest queries per Query Store database, split
into the last 24 hours and the week before. Rules:
- SQLI-QS-001 plan regression: new plan >= 2x slower than the best plan
from the week before and >= 60 s extra per day; scripts the force
- SQLI-QS-002 forced plan that fails (e.g. NO_INDEX); scripts the unforce
- SQLI-QS-003 parameter sensitivity: very different plans or volatile
durations; suggests PSP optimization / Query Store hints on 2022+
Also: backups taken in the same second a database was created count.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E3EExwndy8Z22EK2Qpt6BK
- ASP.NET Core Blazor static SSR: overview with KPI tiles, instance page
(findings, CPU/batch/PLE trends, top waits, file latency, blocking,
deadlocks with .xdl download, not-measurable rules, resolved), finding
detail with explanation, evidence, fix script and references
- Light-first design (sidebar, cards, self-hosted Inter, teal accent),
optional dark theme via toggle; strict CSP, no inline styles
- Windows authentication (Negotiate/IIS) with optional group restriction;
refuses to run without authentication outside Development
- Hosting: IIS (in-process ASP.NET Core Module), Windows service with
Kestrel, or systemd; web reads the repository with db_datareader only
- docs/installation.md for repository, permissions, agent and web
- Fix: CPU used by other processes is not derived on Linux (SystemIdle is
unreliable there); deadlocks are not re-reported or stored twice after
an agent restart; Dapper sends datetime2 so time comparisons are exact
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E3EExwndy8Z22EK2Qpt6BK
Collectors (SQL Server 2000+, version-specific SQL, least privilege):
server, configuration, trace flags, databases (settings, backups in UTC,
CHECKDB, VLFs, Query Store, files, tempdb), suspect pages, plan cache,
wait stats and file I/O as deltas with restart/clear detection,
performance counters, CPU from the scheduler monitor ring buffer,
blocking (sleeping head blockers with open transactions), and deadlocks
from system_health continuing from the last file offset.
Rules: configuration (memory, MAXDOP per Microsoft NUMA guidance, cost
threshold, ad hoc bloat, risky options, pending changes), databases
(state, settings, backups, log backups, CHECKDB, suspect pages, VLFs,
autogrowth, max size, Query Store, tempdb), waits (THREADPOOL, signal
waits, locks, I/O, WRITELOG, parallelism, PAGELATCH), I/O latency, CPU,
memory grants, blocking and deadlocks. Missing data makes a rule not
measurable with the collector's reason.
Integration tests run every collector as the least-privilege login and
reproduce real blocking and a real deadlock; green on 2017 and 2022.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E3EExwndy8Z22EK2Qpt6BK