internal/rules.go (257 lines of code) (raw):

/* Copyright 2023 Google LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package internal const ( // PowerProfileSettingRule used for power profile of machine. PowerProfileSettingRule = "power_profile_setting" // LocalSSDRule used to connect physical drive to disk type. LocalSSDRule = "local_ssd" // LogicalDiskToPartition info used for windows os collection. LogicalDiskToPartition = "logical_disk_to_partition" // PhysicalDiskToType info used for windows os collection. PhysicalDiskToType = "physical_disk_to_type" // DataDiskAllocationUnitsRule used to see blocksize of a physical drive. DataDiskAllocationUnitsRule = "data_disk_allocation_units" // GCBDRAgentRunning used for checking if GCBDRAgentRunning is running on the target. GCBDRAgentRunning = "gcbdr_agent_running" ) // Details represents collected details results. type Details struct { Name string Fields []map[string]string } // MasterRuleStruct defines the data struct of sql server master rules. type MasterRuleStruct struct { // Name defines the rule name. Name string // Query is the sql query statement for the rule. Query string // Fields returns the <key, value> of collected columns and values. Different rules query // different tables and columns. Fields func([][]any) []map[string]string } // MasterRules defines the rules the agent will collect from sql server. var MasterRules = []MasterRuleStruct{ { Name: "DB_LOG_DISK_SEPARATION", Query: `SELECT type, d.name, physical_name, m.state, size, growth, is_percent_growth FROM sys.master_files m JOIN sys.databases d ON m.database_id = d.database_id`, Fields: func(fields [][]any) []map[string]string { res := []map[string]string{} for _, f := range fields { res = append(res, map[string]string{ "db_name": HandleNilString(f[1]), "filetype": HandleNilInt(f[0]), "physical_name": HandleNilString(f[2]), "physical_drive": "unknown", "state": HandleNilInt(f[3]), "size": HandleNilInt(f[4]), "growth": HandleNilInt(f[5]), "is_percent_growth": HandleNilBool(f[6]), }) } return res }, }, { Name: "DB_MAX_PARALLELISM", Query: `SELECT value_in_use as maxDegreeOfParallelism FROM sys.configurations WHERE name = 'max degree of parallelism'`, Fields: func(fields [][]any) []map[string]string { res := []map[string]string{} for _, f := range fields { res = append(res, map[string]string{ "maxDegreeOfParallelism": HandleNilInt(f[0]), }) } return res }, }, { Name: "DB_TRANSACTION_LOG_HANDLING", Query: `WITH cte AS ( SELECT d.name, MAX(b.backup_finish_date) AS backup_finish_date, MAX(m.growth) AS growth FROM master.sys.sysdatabases d LEFT JOIN msdb.dbo.backupset b ON b.database_name = d.name AND b.type = 'L' LEFT JOIN sys.master_files m ON d.dbid = m.database_id AND m.type = 1 WHERE d.name NOT IN ('master', 'tempdb', 'model', 'msdb') GROUP BY d.name ) SELECT cte.name, CASE WHEN b.backup_finish_date IS NULL THEN 100000 ELSE DATEDIFF(HOUR, b.backup_finish_date, GETDATE()) END AS [backup_age], b.backup_size, b.compressed_backup_size, CASE WHEN growth > 0 THEN 1 ELSE 0 END AS auto_growth FROM cte LEFT JOIN msdb.dbo.backupset b ON b.database_name = cte.name AND b.backup_finish_date = cte.backup_finish_date`, Fields: func(fields [][]any) []map[string]string { res := []map[string]string{} for _, f := range fields { res = append(res, map[string]string{ "db_name": HandleNilString(f[0]), "backup_age_in_hours": HandleNilInt(f[1]), "backup_size": HandleNilInt(f[2]), "compressed_backup_size": HandleNilInt(f[3]), "auto_growth": HandleNilInt(f[4]), }) } return res }, }, { Name: "DB_VIRTUAL_LOG_FILE_COUNT", Query: `SELECT [name], COUNT(l.database_id) AS 'VLFCount', SUM(vlf_size_mb) AS 'VLFSizeInMB', SUM(CAST(vlf_active AS INT)) AS 'ActiveVLFCount', SUM(vlf_active*vlf_size_mb) AS 'ActiveVLFSizeInMB' FROM sys.databases s CROSS APPLY sys.dm_db_log_info(s.database_id) l WHERE [name] NOT IN ('master', 'tempdb', 'model', 'msdb') GROUP BY [name]`, Fields: func(fields [][]any) []map[string]string { res := []map[string]string{} for _, f := range fields { res = append(res, map[string]string{ "db_name": HandleNilString(f[0]), "vlf_count": HandleNilInt(f[1]), "vlf_size_in_mb": HandleNilFloat64(f[2]), "active_vlf_count": HandleNilInt(f[3]), "active_vlf_size_in_mb": HandleNilFloat64(f[4]), }) } return res }, }, { Name: "DB_BUFFER_POOL_EXTENSION", Query: `SELECT path, state, current_size_in_kb FROM sys.dm_os_buffer_pool_extension_configuration`, Fields: func(fields [][]any) []map[string]string { res := []map[string]string{} for _, f := range fields { res = append(res, map[string]string{ "path": HandleNilString(f[0]), "state": HandleNilInt(f[1]), "size_in_kb": HandleNilInt(f[2]), }) } return res }, }, { Name: "DB_MAX_SERVER_MEMORY", Query: `SELECT [name], [value], [value_in_use] FROM sys.configurations WHERE [name] = 'max server memory (MB)';`, Fields: func(fields [][]any) []map[string]string { res := []map[string]string{} for _, f := range fields { res = append(res, map[string]string{ "name": HandleNilString(f[0]), "value": HandleNilInt(f[1]), "value_in_use": HandleNilInt(f[2]), }) } return res }, }, { Name: "DB_INDEX_FRAGMENTATION", Query: `SELECT top 1 1 AS found_index_fragmentation FROM sys.databases d CROSS APPLY sys.dm_db_index_physical_stats (d.database_id, NULL, NULL, NULL, NULL) AS DDIPS WHERE ddips.avg_fragmentation_in_percent > 95 AND d.name NOT IN ('master', 'model', 'msdb', 'tempdb') And d.name NOT IN ( SELECT DISTINCT dbcs.database_name AS [DatabaseName] FROM master.sys.availability_groups AS AG INNER JOIN master.sys.availability_replicas AS AR ON AG.group_id = AR.group_id INNER JOIN master.sys.dm_hadr_availability_replica_states AS arstates ON AR.replica_id = arstates.replica_id AND arstates.is_local = 1 INNER JOIN master.sys.dm_hadr_database_replica_cluster_states AS dbcs ON arstates.replica_id = dbcs.replica_id WHERE ISNULL(arstates.role, 3) = 2 AND ISNULL(dbcs.is_database_joined, 0) = 1)`, Fields: func(fields [][]any) []map[string]string { res := []map[string]string{} for _, f := range fields { res = append(res, map[string]string{ "found_index_fragmentation": HandleNilInt(f[0]), }) } return res }, }, { Name: "DB_TABLE_INDEX_COMPRESSION", Query: `SELECT COUNT(*) numOfPartitionsWithCompressionEnabled FROM sys.partitions p WHERE data_compression <> 0 and rows > 0`, Fields: func(fields [][]any) []map[string]string { res := []map[string]string{} for _, f := range fields { res = append(res, map[string]string{ "numOfPartitionsWithCompressionEnabled": HandleNilInt(f[0]), }) } return res }, }, { Name: "INSTANCE_METRICS", Query: `SELECT SERVERPROPERTY('productversion') AS productversion, SERVERPROPERTY ('productlevel') AS productlevel, SERVERPROPERTY ('edition') AS edition, cpu_count AS cpuCount, hyperthread_ratio AS hyperthreadRatio, physical_memory_kb AS physicalMemoryKb, virtual_memory_kb AS virtualMemoryKb, socket_count AS socketCount, cores_per_socket AS coresPerSocket, numa_node_count AS numaNodeCount FROM sys.dm_os_sys_info`, Fields: func(fields [][]any) []map[string]string { res := []map[string]string{} for _, f := range fields { res = append(res, map[string]string{ "os": HandleNilString(f[10]), "product_version": HandleNilString(f[0]), "product_level": HandleNilString(f[1]), "edition": HandleNilString(f[2]), "cpu_count": HandleNilInt(f[3]), "hyperthread_ratio": HandleNilInt(f[4]), "physical_memory_kb": HandleNilInt(f[5]), "virtual_memory_kb": HandleNilInt(f[6]), "socket_count": HandleNilInt(f[7]), "cores_per_socket": HandleNilInt(f[8]), "numa_node_count": HandleNilInt(f[9]), }) } return res }, }, { Name: "DB_BACKUP_POLICY", Query: `WITH cte AS ( SELECT master.sys.sysdatabases.NAME AS database_name, CASE WHEN MAX(msdb.dbo.backupset.backup_finish_date) IS NULL THEN 100000 ELSE DATEDIFF(DAY, MAX(msdb.dbo.backupset.backup_finish_date), GETDATE()) END AS [backup_age] FROM master.sys.sysdatabases LEFT JOIN msdb.dbo.backupset ON master.sys.sysdatabases.name = msdb.dbo.backupset.database_name WHERE master.sys.sysdatabases.name NOT IN ('master', 'model', 'msdb', 'tempdb' ) GROUP BY master.sys.sysdatabases.name HAVING MAX(msdb.dbo.backupset.backup_finish_date) IS NULL OR (MAX(msdb.dbo.backupset.backup_finish_date) < DATEADD(hh, - 24, GETDATE())) ) SELECT MAX(backup_age) as maxBackupAge FROM cte`, Fields: func(fields [][]any) []map[string]string { res := []map[string]string{} for _, f := range fields { res = append(res, map[string]string{ "max_backup_age": HandleNilInt(f[0]), }) } return res }, }, }