sqlinsight-pro/SQL Scripts/SQLI-CPU-002.sql
Søren Eggert Lundsteen Olsen 2284e7f082 SQL Scripts: one reviewable script per rule
A DBA should be able to review every statement SQLInsight sends to a
monitored server before granting it access. "SQL Scripts/<rule-id>.sql"
holds, per rule (82 built-in, 16 community), a Danish header (what and
why, how C# judges the data, thresholds with config keys, severity,
exact GRANTs, versions, collector file and member) and the collector's
SQL verbatim, every version variant as its own batch.
"_collectors/<Collector>.sql" holds each collector's queries once.

The only statement that is not strictly read-only is documented in the
scripts: the deadlock collector's session temp table (#system_health).

Inline SQL in the collectors moved to internal constants and Build*Sql
methods (no text changed), so SqlScriptTests can check that the scripts
still contain the SQL the code sends, that every rule has a script, that
every collector query is registered, and that nothing writes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 13:20:06 +02:00

130 lines
8.4 KiB
Transact-SQL

-- ===================================================================================================================
-- SQLI-CPU-002: Sætning kører næsten uafbrudt
-- ===================================================================================================================
-- Regel-id: SQLI-CPU-002
-- Titel: Sætning kører næsten uafbrudt
-- Alvor: Medium
-- Versioner: SQL Server 2005 og nyere.
--
-- Hvad tjekkes, og hvorfor:
-- Om en sætning i praksis holder en CPU-kerne beskæftiget hele tiden.
--
-- Vurdering (sker i C# ud fra de indsamlede data, ikke i SQL'en, medmindre andet er nævnt):
-- For hver sætning (query_hash; på 2005 sql_handle + offsets) i top SQL: aktivitet = total_worker_time /
-- (last_execution_time - creation_time) i procent af én kerne. Fund (op til 5, højeste først) for sætninger, der
-- har kørt inden for de seneste 15 minutter, har ligget mindst 1 time i plan cache og har aktivitet over grænsen.
--
-- Grænseværdier (konfigurationsnøgle i agentens appsettings.json og standardværdi):
-- SQLInsight:TopSql:ActivityThresholdPercent, standard 50 (%); mindst 1 time i plan cache
-- (ConstantActivityRule.MinimumLifetime, fast i koden).
--
-- Rettigheder til agentens login (se db/permissions/grant-least-privilege.sql; [agent] = agentens login):
-- GRANT VIEW SERVER STATE TO [agent]; -- SQL Server 2005-2019
-- GRANT VIEW SERVER PERFORMANCE STATE TO [agent]; -- SQL Server 2022+ (erstatter VIEW SERVER STATE)
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
--
-- Data kommer fra (collector, fil : medlemmer, interval):
-- QueryStatsCollector
-- Fil: src/SQLInsight.Collectors/Performance/QueryStatsCollector.cs
-- Medlemmer: Db.StartTimeSql(version), QueryStatsCollector.Sql2008 via SqlFor(version, since: null),
-- QueryStatsCollector.Sql2005 via SqlFor(version, since: null), QueryStatsCollector.ServerTimeSql
-- Interval: 5 min som standard (SQLInsight:CollectorIntervals:query-stats)
-- Delt script: SQL Scripts/_collectors/QueryStatsCollector.sql
-- Regelkode: src/SQLInsight.Rules/Performance/OperationRules.cs : ConstantActivityRule.Evaluate
--
-- Read-only: Ja: kun SELECT samt SET-sessionsindstillinger. Intet ændrer data, indstillinger eller objekter.
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
-- database, du vil se.
-- ===================================================================================================================
-- -------------------------------------------------------------------------------------------------------------------
-- Forbindelsen: Sessionsindstillinger, agenten sætter på hver forbindelse
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: Alle versioner: sættes på hver forbindelse, agenten åbner
-- Kilde: SqlConnectionFactory.SessionSettings
-- Kode: src/SQLInsight.Connectivity/SqlConnectionFactory.cs : SqlConnectionFactory.SessionSettings
SET LOCK_TIMEOUT 5000; SET DEADLOCK_PRIORITY LOW; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
GO
-- -------------------------------------------------------------------------------------------------------------------
-- QueryStatsCollector: Top SQL: de tungeste sætninger (top 50 efter CPU, varighed, læsninger og antal) blandt dem,
-- der har kørt siden forrige måling. Ingen planer, tekst afkortet.
-- -------------------------------------------------------------------------------------------------------------------
-- Variant: SQL Server 2008 og nyere: starttidspunkt
-- Kilde: QueryStatsCollector.StartTimeSql(2008+)
-- Kode: src/SQLInsight.Collectors/Db.cs : Db.StartTimeSql(version)
SELECT (SELECT sqlserver_start_time FROM sys.dm_os_sys_info)
GO
-- Variant: SQL Server 2005: starttidspunkt
-- Kilde: QueryStatsCollector.StartTimeSql(2005)
-- Kode: src/SQLInsight.Collectors/Db.cs : Db.StartTimeSql(version)
SELECT (SELECT create_date FROM sys.databases WHERE database_id = 2)
GO
-- Variant: SQL Server 2008 og nyere (første kørsel: de seneste 15 minutter)
-- Kilde: QueryStatsCollector.SqlFor(2008+)
-- Kode: src/SQLInsight.Collectors/Performance/QueryStatsCollector.cs : QueryStatsCollector.Sql2008 via SqlFor(version, since: null)
WITH s AS (
SELECT qs.query_hash AS k,
SUM(qs.execution_count) AS execs, SUM(qs.total_worker_time) AS cpu, SUM(qs.total_elapsed_time) AS elapsed,
SUM(qs.total_logical_reads) AS lreads, SUM(qs.total_physical_reads) AS preads, SUM(qs.total_logical_writes) AS writes,
MIN(qs.creation_time) AS created, MAX(qs.last_execution_time) AS last_exec
FROM sys.dm_exec_query_stats qs
GROUP BY qs.query_hash
HAVING MAX(qs.last_execution_time) >= DATEADD(minute, -15, GETDATE())),
t AS (
SELECT k FROM (SELECT TOP (50) k FROM s ORDER BY cpu DESC) a
UNION SELECT k FROM (SELECT TOP (50) k FROM s ORDER BY elapsed DESC) b
UNION SELECT k FROM (SELECT TOP (50) k FROM s ORDER BY lreads DESC) c
UNION SELECT k FROM (SELECT TOP (50) k FROM s ORDER BY execs DESC) d)
SELECT CONVERT(varchar(20), s.k, 1), s.execs, s.cpu, s.elapsed, s.lreads, s.preads, s.writes, s.created, s.last_exec,
DB_NAME(COALESCE(st.dbid, pa.dbid)),
CAST(SUBSTRING(st.text, r.statement_start_offset / 2 + 1,
CASE WHEN r.statement_end_offset = -1 THEN 4000
ELSE (r.statement_end_offset - r.statement_start_offset) / 2 + 1 END) AS nvarchar(4000)),
GETDATE()
FROM s JOIN t ON t.k = s.k
OUTER APPLY (SELECT TOP (1) q.sql_handle, q.plan_handle, q.statement_start_offset, q.statement_end_offset
FROM sys.dm_exec_query_stats q WHERE q.query_hash = s.k ORDER BY q.last_execution_time DESC) r
OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) st
OUTER APPLY (SELECT CAST(a.value AS int) AS dbid FROM sys.dm_exec_plan_attributes(r.plan_handle) a WHERE a.attribute = N'dbid') pa
GO
-- Variant: SQL Server 2005 (første kørsel: de seneste 15 minutter)
-- Kilde: QueryStatsCollector.SqlFor(2005)
-- Kode: src/SQLInsight.Collectors/Performance/QueryStatsCollector.cs : QueryStatsCollector.Sql2005 via SqlFor(version, since: null)
WITH s AS (
SELECT qs.sql_handle AS h, qs.statement_start_offset AS so, qs.statement_end_offset AS eo,
SUM(qs.execution_count) AS execs, SUM(qs.total_worker_time) AS cpu, SUM(qs.total_elapsed_time) AS elapsed,
SUM(qs.total_logical_reads) AS lreads, SUM(qs.total_physical_reads) AS preads, SUM(qs.total_logical_writes) AS writes,
MIN(qs.creation_time) AS created, MAX(qs.last_execution_time) AS last_exec
FROM sys.dm_exec_query_stats qs
GROUP BY qs.sql_handle, qs.statement_start_offset, qs.statement_end_offset
HAVING MAX(qs.last_execution_time) >= DATEADD(minute, -15, GETDATE())),
t AS (
SELECT h, so, eo FROM (SELECT TOP (50) h, so, eo FROM s ORDER BY cpu DESC) a
UNION SELECT h, so, eo FROM (SELECT TOP (50) h, so, eo FROM s ORDER BY elapsed DESC) b
UNION SELECT h, so, eo FROM (SELECT TOP (50) h, so, eo FROM s ORDER BY lreads DESC) c
UNION SELECT h, so, eo FROM (SELECT TOP (50) h, so, eo FROM s ORDER BY execs DESC) d)
SELECT sys.fn_varbintohexstr(s.h) + ':' + CAST(s.so AS varchar(12)) + ':' + CAST(s.eo AS varchar(12)),
s.execs, s.cpu, s.elapsed, s.lreads, s.preads, s.writes, s.created, s.last_exec,
DB_NAME(COALESCE(st.dbid, pa.dbid)),
CAST(SUBSTRING(st.text, s.so / 2 + 1, CASE WHEN s.eo = -1 THEN 4000 ELSE (s.eo - s.so) / 2 + 1 END) AS nvarchar(4000)),
GETDATE()
FROM s JOIN t ON t.h = s.h AND t.so = s.so AND t.eo = s.eo
OUTER APPLY sys.dm_exec_sql_text(s.h) st
OUTER APPLY (SELECT TOP (1) q.plan_handle FROM sys.dm_exec_query_stats q
WHERE q.sql_handle = s.h AND q.statement_start_offset = s.so AND q.statement_end_offset = s.eo) r
OUTER APPLY (SELECT CAST(a.value AS int) AS dbid FROM sys.dm_exec_plan_attributes(r.plan_handle) a WHERE a.attribute = N'dbid') pa
GO
-- Variant: Alle versioner, kun når ingen sætning har kørt siden sidst
-- Kilde: QueryStatsCollector.ServerTimeSql
-- Kode: src/SQLInsight.Collectors/Performance/QueryStatsCollector.cs : QueryStatsCollector.ServerTimeSql
SELECT GETDATE()
GO