1
0
Fork 0

Adding upstream version 1.34.4.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-05-24 07:26:29 +02:00
parent e393c3af3f
commit 4978089aab
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
4963 changed files with 677545 additions and 0 deletions

View file

@ -0,0 +1,537 @@
# SQL Server Input Plugin
The `sqlserver` plugin provides metrics for your SQL Server instance.
Recorded metrics are lightweight and use Dynamic Management Views
supplied by SQL Server.
## The SQL Server plugin supports the following editions/versions of SQL Server
- SQL Server
- 2012 or newer (Plugin support aligned with the [official Microsoft SQL Server support](https://docs.microsoft.com/en-us/sql/sql-server/end-of-support/sql-server-end-of-life-overview?view=sql-server-ver15#lifecycle-dates))
- End-of-life SQL Server versions are not guaranteed to be supported by Telegraf. Any issues with the SQL Server plugin for these EOL versions will need to be addressed by the community.
- Azure SQL Database (Single)
- Azure SQL Managed Instance
- Azure SQL Elastic Pool
- Azure Arc-enabled SQL Managed Instance
## Additional Setup
You have to create a login on every SQL Server instance or Azure SQL
Managed instance you want to monitor, with following script:
```sql
USE master;
GO
CREATE LOGIN [telegraf] WITH PASSWORD = N'mystrongpassword';
GO
GRANT VIEW SERVER STATE TO [telegraf];
GO
GRANT VIEW ANY DEFINITION TO [telegraf];
GO
```
For Azure SQL Database, you require the View Database State permission
and can create a user with a password directly in the database.
```sql
CREATE USER [telegraf] WITH PASSWORD = N'mystrongpassword';
GO
GRANT VIEW DATABASE STATE TO [telegraf];
GO
```
For Azure SQL Elastic Pool, please follow the following instructions
to collect metrics.
On master logical database, create an SQL login 'telegraf' and assign
it to the server-level role ##MS_ServerStateReader##.
```sql
CREATE LOGIN [telegraf] WITH PASSWORD = N'mystrongpassword';
GO
ALTER SERVER ROLE ##MS_ServerStateReader##
ADD MEMBER [telegraf];
GO
```
Elastic pool metrics can be collected from any database in the pool if a user
for the `telegraf` login is created in that database. For collection to work,
this database must remain in the pool, and must not be renamed. If you plan
to add/remove databases from this pool, create a separate database for
monitoring purposes that will remain in the pool.
> Note: To avoid duplicate monitoring data, do not collect elastic pool metrics
from more than one database in the same pool.
```sql
GO
CREATE USER [telegraf] FOR LOGIN telegraf;
```
For Service SID authentication to SQL Server (Windows service installations
only).
- [More information about using service SIDs to grant permissions in SQL Server](https://docs.microsoft.com/en-us/sql/relational-databases/security/using-service-sids-to-grant-permissions-to-services-in-sql-server)
In an administrative command prompt configure the telegraf service for use
with a service SID
```Batchfile
sc.exe sidtype "telegraf" unrestricted
```
To create the login for the telegraf service run the following script:
```sql
USE master;
GO
CREATE LOGIN [NT SERVICE\telegraf] FROM WINDOWS;
GO
GRANT VIEW SERVER STATE TO [NT SERVICE\telegraf];
GO
GRANT VIEW ANY DEFINITION TO [NT SERVICE\telegraf];
GO
```
Remove User Id and Password keywords from the connection string in your
config file to use windows authentication.
```toml
[[inputs.sqlserver]]
servers = ["Server=192.168.1.10;Port=1433;app name=telegraf;log=1;",]
```
To set up a configurable timeout, add timeout to the connections string
in your config file.
```toml
servers = [
"Server=192.168.1.10;Port=1433;User Id=<user>;Password=<pw>;app name=telegraf;log=1;dial timeout=30",
]
```
## Global configuration options <!-- @/docs/includes/plugin_config.md -->
In addition to the plugin-specific configuration settings, plugins support
additional global and plugin configuration settings. These settings are used to
modify metrics, tags, and field or create aliases and configure ordering, etc.
See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
[CONFIGURATION.md]: ../../../docs/CONFIGURATION.md#plugins
## Secret-store support
This plugin supports secrets from secret-stores for the `servers` option.
See the [secret-store documentation][SECRETSTORE] for more details on how
to use them.
[SECRETSTORE]: ../../../docs/CONFIGURATION.md#secret-store-secrets
## Configuration
```toml @sample.conf
# Read metrics from Microsoft SQL Server
[[inputs.sqlserver]]
## Specify instances to monitor with a list of connection strings.
## All connection parameters are optional.
## By default, the host is localhost, listening on default port, TCP 1433.
## for Windows, the user is the currently running AD user (SSO).
## See https://github.com/microsoft/go-mssqldb for detailed connection
## parameters, in particular, tls connections can be created like so:
## "encrypt=true;certificate=<cert>;hostNameInCertificate=<SqlServer host fqdn>"
servers = [
"Server=192.168.1.10;Port=1433;User Id=<user>;Password=<pw>;app name=telegraf;log=1;",
]
## Timeout for query execution operation
## Note that the timeout for queries is per query not per gather.
## 0 value means no timeout
# query_timeout = "0s"
## Authentication method
## valid methods: "connection_string", "AAD"
# auth_method = "connection_string"
## ClientID is the is the client ID of the user assigned identity of the VM
## that should be used to authenticate to the Azure SQL server.
# client_id = ""
## "database_type" enables a specific set of queries depending on the database type. If specified, it replaces azuredb = true/false and query_version = 2
## In the config file, the sql server plugin section should be repeated each with a set of servers for a specific database_type.
## Possible values for database_type are - "SQLServer" or "AzureSQLDB" or "AzureSQLManagedInstance" or "AzureSQLPool"
database_type = "SQLServer"
## A list of queries to include. If not specified, all the below listed queries are used.
include_query = []
## A list of queries to explicitly ignore.
exclude_query = ["SQLServerAvailabilityReplicaStates", "SQLServerDatabaseReplicaStates"]
## Force using the deprecated ADAL authentication method instead of the recommended
## MSAL method. Setting this option is not recommended and only exists for backward
## compatibility.
# use_deprecated_adal_authentication = false
## Queries enabled by default for database_type = "SQLServer" are -
## SQLServerPerformanceCounters, SQLServerWaitStatsCategorized, SQLServerDatabaseIO, SQLServerProperties, SQLServerMemoryClerks,
## SQLServerSchedulers, SQLServerRequests, SQLServerVolumeSpace, SQLServerCpu, SQLServerAvailabilityReplicaStates, SQLServerDatabaseReplicaStates,
## SQLServerRecentBackups
## Queries enabled by default for database_type = "AzureSQLDB" are -
## AzureSQLDBResourceStats, AzureSQLDBResourceGovernance, AzureSQLDBWaitStats, AzureSQLDBDatabaseIO, AzureSQLDBServerProperties,
## AzureSQLDBOsWaitstats, AzureSQLDBMemoryClerks, AzureSQLDBPerformanceCounters, AzureSQLDBRequests, AzureSQLDBSchedulers
## Queries enabled by default for database_type = "AzureSQLManagedInstance" are -
## AzureSQLMIResourceStats, AzureSQLMIResourceGovernance, AzureSQLMIDatabaseIO, AzureSQLMIServerProperties, AzureSQLMIOsWaitstats,
## AzureSQLMIMemoryClerks, AzureSQLMIPerformanceCounters, AzureSQLMIRequests, AzureSQLMISchedulers
## Queries enabled by default for database_type = "AzureSQLPool" are -
## AzureSQLPoolResourceStats, AzureSQLPoolResourceGovernance, AzureSQLPoolDatabaseIO, AzureSQLPoolWaitStats,
## AzureSQLPoolMemoryClerks, AzureSQLPoolPerformanceCounters, AzureSQLPoolSchedulers
## Queries enabled by default for database_type = "AzureArcSQLManagedInstance" are -
## AzureSQLMIDatabaseIO, AzureSQLMIServerProperties, AzureSQLMIOsWaitstats,
## AzureSQLMIMemoryClerks, AzureSQLMIPerformanceCounters, AzureSQLMIRequests, AzureSQLMISchedulers
## Following are old config settings
## You may use them only if you are using the earlier flavor of queries, however it is recommended to use
## the new mechanism of identifying the database_type there by use it's corresponding queries
## Optional parameter, setting this to 2 will use a new version
## of the collection queries that break compatibility with the original
## dashboards.
## Version 2 - is compatible from SQL Server 2012 and later versions and also for SQL Azure DB
# query_version = 2
## If you are using AzureDB, setting this to true will gather resource utilization metrics
# azuredb = false
## Toggling this to true will emit an additional metric called "sqlserver_telegraf_health".
## This metric tracks the count of attempted queries and successful queries for each SQL instance specified in "servers".
## The purpose of this metric is to assist with identifying and diagnosing any connectivity or query issues.
## This setting/metric is optional and is disabled by default.
# health_metric = false
## Possible queries across different versions of the collectors
## Queries enabled by default for specific Database Type
## database_type = AzureSQLDB by default collects the following queries
## - AzureSQLDBWaitStats
## - AzureSQLDBResourceStats
## - AzureSQLDBResourceGovernance
## - AzureSQLDBDatabaseIO
## - AzureSQLDBServerProperties
## - AzureSQLDBOsWaitstats
## - AzureSQLDBMemoryClerks
## - AzureSQLDBPerformanceCounters
## - AzureSQLDBRequests
## - AzureSQLDBSchedulers
## database_type = AzureSQLManagedInstance by default collects the following queries
## - AzureSQLMIResourceStats
## - AzureSQLMIResourceGovernance
## - AzureSQLMIDatabaseIO
## - AzureSQLMIServerProperties
## - AzureSQLMIOsWaitstats
## - AzureSQLMIMemoryClerks
## - AzureSQLMIPerformanceCounters
## - AzureSQLMIRequests
## - AzureSQLMISchedulers
## database_type = AzureSQLPool by default collects the following queries
## - AzureSQLPoolResourceStats
## - AzureSQLPoolResourceGovernance
## - AzureSQLPoolDatabaseIO
## - AzureSQLPoolOsWaitStats,
## - AzureSQLPoolMemoryClerks
## - AzureSQLPoolPerformanceCounters
## - AzureSQLPoolSchedulers
## database_type = SQLServer by default collects the following queries
## - SQLServerPerformanceCounters
## - SQLServerWaitStatsCategorized
## - SQLServerDatabaseIO
## - SQLServerProperties
## - SQLServerMemoryClerks
## - SQLServerSchedulers
## - SQLServerRequests
## - SQLServerVolumeSpace
## - SQLServerCpu
## - SQLServerRecentBackups
## and following as optional (if mentioned in the include_query list)
## - SQLServerAvailabilityReplicaStates
## - SQLServerDatabaseReplicaStates
```
## Support for Azure Active Directory (AAD) authentication using [Managed Identity](https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview)
- Azure SQL Database supports 2 main methods of authentication: [SQL authentication and AAD authentication](https://docs.microsoft.com/en-us/azure/azure-sql/database/security-overview#authentication).
- The recommended practice is to [use AAD authentication when possible](https://docs.microsoft.com/en-us/azure/azure-sql/database/authentication-aad-overview).
AAD is a more modern authentication protocol, allows for easier
credential/role management, and can eliminate the need to include passwords
in a connection string.
To enable support for AAD authentication, we leverage the existing AAD
authentication support.
If more then one managed identity is assigned to the VM. You need specify the
client_id of the identity you wish to use to authenticate with the SQL Server.
If only one is assigned you don't need so specify this value.
- Please see [SQL Server driver for Go](https://github.com/microsoft/go-mssqldb#azure-active-directory-authentication)
### How to use AAD Auth with MSI
- Please note AAD based auth is currently only supported for Azure SQL Database and Azure SQL Managed Instance (but not for SQL Server), as described [here](https://docs.microsoft.com/en-us/azure/azure-sql/database/security-overview#authentication).
- Configure "system-assigned managed identity" for Azure resources on the Monitoring VM (the VM that'd connect to the SQL server/database) [using the Azure portal](https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/qs-configure-portal-windows-vm).
- On the database being monitored, create/update a USER with the name of the Monitoring VM as the principal using the below script. This might require allow-listing the client machine's IP address (from where the below SQL script is being run) on the SQL Server resource.
In case of multiple assigned identities on one VM you can use the parameter
user_assigned_id to specify the client_id.
```sql
EXECUTE ('IF EXISTS(SELECT * FROM sys.database_principals WHERE name = ''<Monitoring_VM_Name>'')
BEGIN
DROP USER [<Monitoring_VM_Name>]
END')
EXECUTE ('CREATE USER [<Monitoring_VM_Name>] FROM EXTERNAL PROVIDER')
EXECUTE ('GRANT VIEW DATABASE STATE TO [<Monitoring_VM_Name>]')
```
- On the SQL Server resource of the database(s) being monitored, go to "Firewalls and Virtual Networks" tab and allowlist the monitoring VM IP address.
- On the Monitoring VM, update the telegraf config file with the database connection string in the following format. The connection string only provides the server and database name, but no password (since the VM's system-assigned managed identity would be used for authentication). The auth method must be set to "AAD"
```toml
servers = [
"Server=<Azure_SQL_Server_Name>.database.windows.net;Port=1433;Database=<Azure_SQL_Database_Name>;app name=telegraf;log=1;",
]
auth_method = "AAD"
```
## Metrics
To provide backwards compatibility, this plugin support two versions of
metrics queries.
**Note**: Version 2 queries are not backwards compatible with the old queries.
Any dashboards or queries based on the old query format will not work with
the new format. The version 2 queries only report raw metrics, no math has
been done to calculate deltas. To graph this data you must calculate deltas
in your dashboarding software.
### Version 1 (query_version=1): This is Deprecated in 1.16, all future development will be under configuration option database_type
The original metrics queries provide:
- *Performance counters*: 1000+ metrics from `sys.dm_os_performance_counters`
- *Performance metrics*: special performance and ratio metrics
- *Wait stats*: wait tasks categorized from `sys.dm_os_wait_stats`
- *Memory clerk*: memory breakdown from `sys.dm_os_memory_clerks`
- *Database size*: databases size trend from `sys.dm_io_virtual_file_stats`
- *Database IO*: databases I/O from `sys.dm_io_virtual_file_stats`
- *Database latency*: databases latency from `sys.dm_io_virtual_file_stats`
- *Database properties*: databases properties, state and recovery model, from `sys.databases`
- *OS Volume*: available, used and total space from `sys.dm_os_volume_stats`
- *CPU*: cpu usage from `sys.dm_os_ring_buffers`
If you are using the original queries all stats have the following tags:
- `servername`: hostname:instance
- `type`: type of stats to easily filter measurements
### Version 2 (query_version=2): This is Deprecated in 1.16, all future development will be under configuration option database_type
The new (version 2) metrics provide:
- *Database IO*: IO stats from `sys.dm_io_virtual_file_stats`.
- *Memory Clerk*: Memory clerk breakdown from `sys.dm_os_memory_clerks`, most clerks have been given a friendly name.
- *Performance Counters*: A select list of performance counters from `sys.dm_os_performance_counters`. Some of the important metrics included:
- *Activity*: Transactions/sec/database, Batch requests/sec, blocked processes, + more
- *Availability Groups*: Bytes sent to replica, Bytes received from replica, Log bytes received, Log send queue, transaction delay, + more
- *Log activity*: Log bytes flushed/sec, Log flushes/sec, Log Flush Wait Time
- *Memory*: PLE, Page reads/sec, Page writes/sec, + more
- *TempDB*: Free space, Version store usage, Active temp tables, temp table creation rate, + more
- *Resource Governor*: CPU Usage, Requests/sec, Queued Requests, and Blocked tasks per workload group + more
- *Server properties*: Number of databases in all possible states (online, offline, suspect, etc.), cpu count, total physical memory, available physical memory, SQL Server service uptime, SQL Server SPID, and SQL Server version. In the case of Azure SQL relevant properties such as Tier, #Vcores, Memory etc.
- *Wait stats*: Wait time in ms, number of waiting tasks, resource wait time, signal wait time, max wait time in ms, wait type, and wait category. The waits are categorized using the same categories used in Query Store.
- *Schedulers* - This captures `sys.dm_os_schedulers`.
- *SqlRequests* - This captures a snapshot of `sys.dm_exec_requests` and `sys.dm_exec_sessions` that gives you running requests as well as wait types and
blocking sessions. Telegraf's monitoring request is omitted unless it is a heading blocker. Also includes sleeping sessions with open transactions.
- *VolumeSpace* - uses `sys.dm_os_volume_stats` to get total, used and occupied space on every disk that contains a data or log file. (Note that even if enabled it won't get any data from Azure SQL Database or SQL Managed Instance). It is pointless to run this with high frequency (ie: every 10s), but it won't cause any problem.
- *Cpu* - uses the buffer ring (`sys.dm_os_ring_buffers`) to get CPU data, the table is updated once per minute. (Note that even if enabled it won't get any data from Azure SQL Database or SQL Managed Instance).
In order to allow tracking on a per statement basis this query produces a
unique tag for each query. Depending on the database workload, this may
result in a high cardinality series. Reference the FAQ for tips on
[managing series cardinality][cardinality].
- *Azure Managed Instances*
- Stats from `sys.server_resource_stats`
- Resource governance stats from `sys.dm_instance_resource_governance`
- *Azure SQL Database* in addition to other stats
- Stats from `sys.dm_db_wait_stats`
- Resource governance stats from `sys.dm_user_db_resource_governance`
- Stats from `sys.dm_db_resource_stats`
### database_type = "AzureSQLDB"
These are metrics for Azure SQL Database (single database) and are very
similar to version 2 but split out for maintenance reasons, better ability
to test,differences in DMVs:
- *AzureSQLDBDatabaseIO*: IO stats from `sys.dm_io_virtual_file_stats` including resource governance time, RBPEX, IO for Hyperscale.
- *AzureSQLDBMemoryClerks*: Memory clerk breakdown from `sys.dm_os_memory_clerks`.
- *AzureSQLDBResourceGovernance*: Relevant properties indicatign resource limits from `sys.dm_user_db_resource_governance`
- *AzureSQLDBPerformanceCounters*: A select list of performance counters from `sys.dm_os_performance_counters` including cloud specific counters for SQL Hyperscale.
- *AzureSQLDBServerProperties*: Relevant Azure SQL relevant properties from such as Tier, #Vcores, Memory etc, storage, etc.
- *AzureSQLDBWaitstats*: Wait time in ms from `sys.dm_db_wait_stats`, number of waiting tasks, resource wait time, signal wait time, max wait time in ms, wait type, and wait category. The waits are categorized using the same categories used in Query Store. These waits are collected only as of the end of the a statement. and for a specific database only.
- *AzureSQLOsWaitstats*: Wait time in ms from `sys.dm_os_wait_stats`, number of waiting tasks, resource wait time, signal wait time, max wait time in ms, wait type, and wait category. The waits are categorized using the same categories used in Query Store. These waits are collected as they occur and instance wide
- *AzureSQLDBRequests*: Requests which are blocked or have a wait type from `sys.dm_exec_sessions` and `sys.dm_exec_requests`. Telegraf's monitoring request is omitted unless it is a heading blocker
- *AzureSQLDBSchedulers* - This captures `sys.dm_os_schedulers` snapshots.
### database_type = "AzureSQLManagedInstance"
These are metrics for Azure SQL Managed instance, are very similar to version
2 but split out for maintenance reasons, better ability to test, differences
in DMVs:
- *AzureSQLMIDatabaseIO*: IO stats from `sys.dm_io_virtual_file_stats` including resource governance time, RBPEX, IO for Hyperscale.
- *AzureSQLMIMemoryClerks*: Memory clerk breakdown from `sys.dm_os_memory_clerks`.
- *AzureSQLMIResourceGovernance*: Relevant properties indicatign resource limits from `sys.dm_instance_resource_governance`
- *AzureSQLMIPerformanceCounters*: A select list of performance counters from `sys.dm_os_performance_counters` including cloud specific counters for SQL Hyperscale.
- *AzureSQLMIServerProperties*: Relevant Azure SQL relevant properties such as Tier, #Vcores, Memory etc, storage, etc.
- *AzureSQLMIOsWaitstats*: Wait time in ms from `sys.dm_os_wait_stats`, number of waiting tasks, resource wait time, signal wait time, max wait time in ms, wait type, and wait category. The waits are categorized using the same categories used in Query Store. These waits are collected as they occur and instance wide
- *AzureSQLMIRequests*: Requests which are blocked or have a wait type from `sys.dm_exec_sessions` and `sys.dm_exec_requests`. Telegraf's monitoring request is omitted unless it is a heading blocker
- *AzureSQLMISchedulers*: This captures `sys.dm_os_schedulers` snapshots.
### database_type = "AzureSQLPool"
These are metrics for Azure SQL to monitor resources usage at Elastic Pool
level. These metrics require additional permissions to be collected, please
ensure to check additional setup section in this documentation.
- *AzureSQLPoolResourceStats*: Returns resource usage statistics for the current elastic pool in a SQL Database server. Queried from `sys.dm_resource_governor_resource_pools_history_ex`.
- *AzureSQLPoolResourceGovernance*: Returns actual configuration and capacity settings used by resource governance mechanisms in the current elastic pool. Queried from `sys.dm_user_db_resource_governance`.
- *AzureSQLPoolDatabaseIO*: Returns I/O statistics for data and log files for each database in the pool. Queried from `sys.dm_io_virtual_file_stats`.
- *AzureSQLPoolOsWaitStats*: Returns information about all the waits encountered by threads that executed. Queried from `sys.dm_os_wait_stats`.
- *AzureSQLPoolMemoryClerks*: Memory clerk breakdown from `sys.dm_os_memory_clerks`.
- *AzureSQLPoolPerformanceCounters*: A selected list of performance counters from `sys.dm_os_performance_counters`. Note: Performance counters where the cntr_type column value is 537003264 are already returned with a percentage format between 0 and 100. For other counters, please check [sys.dm_os_performance_counters](https://docs.microsoft.com/en-us/sql/relational-databases/system-dynamic-management-views/sys-dm-os-performance-counters-transact-sql?view=azuresqldb-current) documentation.
- *AzureSQLPoolSchedulers*: This captures `sys.dm_os_schedulers` snapshots.
### database_type = "SQLServer"
- *SQLServerDatabaseIO*: IO stats from `sys.dm_io_virtual_file_stats`
- *SQLServerMemoryClerks*: Memory clerk breakdown from `sys.dm_os_memory_clerks`, most clerks have been given a friendly name.
- *SQLServerPerformanceCounters*: A select list of performance counters from `sys.dm_os_performance_counters`. Some of the important metrics included:
- *Activity*: Transactions/sec/database, Batch requests/sec, blocked processes, + more
- *Availability Groups*: Bytes sent to replica, Bytes received from replica, Log bytes received, Log send queue, transaction delay, + more
- *Log activity*: Log bytes flushed/sec, Log flushes/sec, Log Flush Wait Time
- *Memory*: PLE, Page reads/sec, Page writes/sec, + more
- *TempDB*: Free space, Version store usage, Active temp tables, temp table creation rate, + more
- *Resource Governor*: CPU Usage, Requests/sec, Queued Requests, and Blocked tasks per workload group + more
- *SQLServerProperties*: Number of databases in all possible states (online, offline, suspect, etc.), cpu count, total physical memory, available physical memory, SQL Server service uptime, SQL Server SPID and SQL Server version. In the case of Azure SQL relevant properties such as Tier, #Vcores, Memory etc.
- *SQLServerWaitStatsCategorized*: Wait time in ms, number of waiting tasks, resource wait time, signal wait time, max wait time in ms, wait type, and wait category. The waits are categorized using the same categories used in Query Store.
- *SQLServerSchedulers*: This captures `sys.dm_os_schedulers`.
- *SQLServerRequests*: This captures a snapshot of `sys.dm_exec_requests` and `sys.dm_exec_sessions` that gives you running requests as well as wait types and
blocking sessions.
- *SQLServerVolumeSpace*: Uses `sys.dm_os_volume_stats` to get total, used and occupied space on every disk that contains a data or log file. (Note that even if enabled it won't get any data from Azure SQL Database or SQL Managed Instance). It is pointless to run this with high frequency (ie: every 10s), but it won't cause any problem.
- SQLServerCpu: Uses the buffer ring (`sys.dm_os_ring_buffers`) to get CPU data, the table is updated once per minute. (Note that even if enabled it won't get any data from Azure SQL Database or SQL Managed Instance).
- SQLServerAvailabilityReplicaStates: Collects availability replica state information from `sys.dm_hadr_availability_replica_states` for a High Availability / Disaster Recovery (HADR) setup
- SQLServerDatabaseReplicaStates: Collects database replica state information from `sys.dm_hadr_database_replica_states` for a High Availability / Disaster Recovery (HADR) setup
- SQLServerRecentBackups: Collects latest full, differential and transaction log backup date and size from `msdb.dbo.backupset`
- SQLServerPersistentVersionStore: Collects persistent version store information from `sys.dm_tran_persistent_version_store_stats` for databases with Accelerated Database Recovery enabled
### Output Measures
The guiding principal is that all data collected from the same primary DMV ends
up in the same measure irrespective of database_type.
- `sqlserver_database_io` - Used by AzureSQLDBDatabaseIO, AzureSQLMIDatabaseIO, SQLServerDatabaseIO, DatabaseIO given the data is from `sys.dm_io_virtual_file_stats`
- `sqlserver_waitstats` - Used by WaitStatsCategorized,AzureSQLDBOsWaitstats,AzureSQLMIOsWaitstats
- `sqlserver_server_properties` - Used by SQLServerProperties, AzureSQLDBServerProperties , AzureSQLMIServerProperties,ServerProperties
- `sqlserver_memory_clerks` - Used by SQLServerMemoryClerks, AzureSQLDBMemoryClerks, AzureSQLMIMemoryClerks,MemoryClerk
- `sqlserver_performance` - Used by SQLServerPerformanceCounters, AzureSQLDBPerformanceCounters, AzureSQLMIPerformanceCounters,PerformanceCounters
- `sys.dm_os_schedulers` - Used by SQLServerSchedulers,AzureSQLDBServerSchedulers, AzureSQLMIServerSchedulers
The following Performance counter metrics can be used directly, with no delta
calculations:
- SQLServer:Buffer Manager\Buffer cache hit ratio
- SQLServer:Buffer Manager\Page life expectancy
- SQLServer:Buffer Node\Page life expectancy
- SQLServer:Database Replica\Log Apply Pending Queue
- SQLServer:Database Replica\Log Apply Ready Queue
- SQLServer:Database Replica\Log Send Queue
- SQLServer:Database Replica\Recovery Queue
- SQLServer:Databases\Data File(s) Size (KB)
- SQLServer:Databases\Log File(s) Size (KB)
- SQLServer:Databases\Log File(s) Used Size (KB)
- SQLServer:Databases\XTP Memory Used (KB)
- SQLServer:General Statistics\Active Temp Tables
- SQLServer:General Statistics\Processes blocked
- SQLServer:General Statistics\Temp Tables For Destruction
- SQLServer:General Statistics\User Connections
- SQLServer:Memory Broker Clerks\Memory broker clerk size
- SQLServer:Memory Manager\Memory Grants Pending
- SQLServer:Memory Manager\Target Server Memory (KB)
- SQLServer:Memory Manager\Total Server Memory (KB)
- SQLServer:Resource Pool Stats\Active memory grant amount (KB)
- SQLServer:Resource Pool Stats\Disk Read Bytes/sec
- SQLServer:Resource Pool Stats\Disk Read IO Throttled/sec
- SQLServer:Resource Pool Stats\Disk Read IO/sec
- SQLServer:Resource Pool Stats\Disk Write Bytes/sec
- SQLServer:Resource Pool Stats\Disk Write IO Throttled/sec
- SQLServer:Resource Pool Stats\Disk Write IO/sec
- SQLServer:Resource Pool Stats\Used memory (KB)
- SQLServer:Transactions\Free Space in tempdb (KB)
- SQLServer:Transactions\Version Store Size (KB)
- SQLServer:User Settable\Query
- SQLServer:Workload Group Stats\Blocked tasks
- SQLServer:Workload Group Stats\CPU usage %
- SQLServer:Workload Group Stats\Queued requests
- SQLServer:Workload Group Stats\Requests completed/sec
Version 2 queries have the following tags:
- `sql_instance`: Physical host and instance name (hostname:instance)
- `database_name`: For Azure SQLDB, database_name denotes the name of the Azure SQL Database as server name is a logical construct.
### Health Metric
All collection versions (version 1, version 2, and database_type) support an
optional plugin health metric called `sqlserver_telegraf_health`. This metric
tracks if connections to SQL Server are succeeding or failing. Users can
leverage this metric to detect if their SQL Server monitoring is not working
as intended.
In the configuration file, toggling `health_metric` to `true` will enable
collection of this metric. By default, this value is set to `false` and
the metric is not collected. The health metric emits one record for each
connection specified by `servers` in the configuration file.
The health metric emits the following tags:
- `sql_instance` - Name of the server specified in the connection string. This value is emitted as-is in the connection string. If the server could not be parsed from the connection string, a constant placeholder value is emitted
- `database_name` - Name of the database or (initial catalog) specified in the connection string. This value is emitted as-is in the connection string. If the database could not be parsed from the connection string, a constant placeholder value is emitted
The health metric emits the following fields:
- `attempted_queries` - Number of queries that were attempted for this connection
- `successful_queries` - Number of queries that completed successfully for this connection
- `database_type` - Type of database as specified by `database_type`. If `database_type` is empty, the `QueryVersion` and `AzureDB` fields are concatenated instead
If `attempted_queries` and `successful_queries` are not equal for
a given connection, some metrics were not successfully gathered for
that connection. If `successful_queries` is 0, no metrics were successfully
gathered.
[cardinality]: /docs/FAQ.md#user-content-q-how-can-i-manage-series-cardinality
## Example Output
```text
sqlserver_cpu_other_process_cpu{host="servername",measurement_db_type="SQLServer",sql_instance="SERVERNAME:INST"} 9
sqlserver_performance{counter="Log File(s) Size (KB)",counter_type="65792",host="servername",instance="instance_name",measurement_db_type="SQLServer",object="MSSQL$INSTANCE_NAME:Databases",sql_instance="SERVERNAME:INSTANCE_NAME"} 1.048568e+06
```

View file

@ -0,0 +1,515 @@
//nolint:lll // conditionally long lines allowed
package sqlserver
import (
_ "github.com/microsoft/go-mssqldb" // go-mssqldb initialization
)
// ------------------------------------------------------------------------------------------------
// ------------------ Azure Arc Managed Instance ------------------------------------------------------
// ------------------------------------------------------------------------------------------------
const sqlAzureArcMIProperties = `
IF SERVERPROPERTY('EngineEdition') <> 10 BEGIN /*not Azure Arc-enabled Managed Instance*/
DECLARE @ErrorMessage AS nvarchar(500) = 'Telegraf - Connection string Server:'+ @@SERVERNAME + ',Database:' + DB_NAME() +' is not an Azure Arc-enabled Managed Instance. Check the database_type parameter in the telegraf configuration.';
RAISERROR (@ErrorMessage,11,1)
RETURN
END
SELECT TOP 1
'sqlserver_server_properties' AS [measurement]
,REPLACE(@@SERVERNAME,'\',':') AS [sql_instance]
,(SELECT [cpu_count] FROM sys.dm_os_sys_info) AS [cpu_count]
,(SELECT [physical_memory_kb] FROM sys.dm_os_sys_info) AS [server_memory]
,(SELECT [host_sku] FROM sys.dm_os_host_info) AS [sku]
,SERVERPROPERTY('EngineEdition') AS [engine_edition]
,(SELECT [container_type_desc] FROM sys.dm_os_sys_info) AS [hardware_type]
,(SELECT DATEDIFF(MINUTE,[sqlserver_start_time],GETDATE()) FROM sys.dm_os_sys_info) as [uptime]
,SERVERPROPERTY('ProductVersion') AS [sql_version]
,LEFT(@@VERSION,CHARINDEX(' - ',@@VERSION)) AS [sql_version_desc]
,(SELECT SUM( CASE WHEN [state] = 0 THEN 1 ELSE 0 END ) FROM sys.databases) AS [db_online]
,(SELECT SUM( CASE WHEN [state] = 1 THEN 1 ELSE 0 END ) FROM sys.databases) AS [db_restoring]
,(SELECT SUM( CASE WHEN [state] = 2 THEN 1 ELSE 0 END ) FROM sys.databases) AS [db_recovering]
,(SELECT SUM( CASE WHEN [state] = 3 THEN 1 ELSE 0 END ) FROM sys.databases) AS [db_recoveryPending]
,(SELECT SUM( CASE WHEN [state] = 4 THEN 1 ELSE 0 END ) FROM sys.databases) AS [db_suspect]
,(SELECT SUM( CASE WHEN [state] IN (6,10) THEN 1 ELSE 0 END) FROM sys.databases) AS [db_offline]
,DATABASEPROPERTYEX(DB_NAME(), 'Updateability') as replica_updateability
`
const sqlAzureArcMIDatabaseIO = `
SET DEADLOCK_PRIORITY -10;
IF SERVERPROPERTY('EngineEdition') <> 10 BEGIN /*not Azure Arc-enabled Managed Instance*/
DECLARE @ErrorMessage AS nvarchar(500) = 'Telegraf - Connection string Server:'+ @@SERVERNAME + ',Database:' + DB_NAME() +' is not an Azure Arc-enabled Managed Instance. Check the database_type parameter in the telegraf configuration.';
RAISERROR (@ErrorMessage,11,1)
RETURN
END
SELECT
'sqlserver_database_io' AS [measurement]
,REPLACE(@@SERVERNAME,'\',':') AS [sql_instance]
,DB_NAME(mf.[database_id]) AS [database_name]
,COALESCE(mf.[physical_name],'RBPEX') AS [physical_filename] --RPBEX = Resilient Buffer Pool Extension
,COALESCE(mf.[name],'RBPEX') AS [logical_filename] --RPBEX = Resilient Buffer Pool Extension
,mf.[type_desc] AS [file_type]
,vfs.[io_stall_read_ms] AS [read_latency_ms]
,vfs.[num_of_reads] AS [reads]
,vfs.[num_of_bytes_read] AS [read_bytes]
,vfs.[io_stall_write_ms] AS [write_latency_ms]
,vfs.[num_of_writes] AS [writes]
,vfs.[num_of_bytes_written] AS [write_bytes]
,vfs.io_stall_queued_read_ms AS [rg_read_stall_ms]
,vfs.io_stall_queued_write_ms AS [rg_write_stall_ms]
,DATABASEPROPERTYEX(DB_NAME(), 'Updateability') as replica_updateability
FROM sys.dm_io_virtual_file_stats(NULL, NULL) AS vfs
LEFT OUTER JOIN sys.master_files AS mf WITH (NOLOCK)
ON vfs.[database_id] = mf.[database_id]
AND vfs.[file_id] = mf.[file_id]
WHERE
vfs.[database_id] < 32760
`
const sqlAzureArcMIMemoryClerks = `
IF SERVERPROPERTY('EngineEdition') <> 10 BEGIN /*not Azure Arc-enabled Managed Instance*/
DECLARE @ErrorMessage AS nvarchar(500) = 'Telegraf - Connection string Server:'+ @@SERVERNAME + ',Database:' + DB_NAME() +' is not an Azure Arc-enabled Managed Instance. Check the database_type parameter in the telegraf configuration.';
RAISERROR (@ErrorMessage,11,1)
RETURN
END
SELECT
'sqlserver_memory_clerks' AS [measurement]
,REPLACE(@@SERVERNAME, '\', ':') AS [sql_instance]
,mc.[type] AS [clerk_type]
,SUM(mc.[pages_kb]) AS [size_kb]
,DATABASEPROPERTYEX(DB_NAME(), 'Updateability') as replica_updateability
FROM sys.[dm_os_memory_clerks] AS mc WITH (NOLOCK)
GROUP BY
mc.[type]
HAVING
SUM(mc.[pages_kb]) >= 1024
OPTION(RECOMPILE);
`
const sqlAzureArcMIOsWaitStats = `
IF SERVERPROPERTY('EngineEdition') <> 10 BEGIN /*not Azure Arc-enabled Managed Instance*/
DECLARE @ErrorMessage AS nvarchar(500) = 'Telegraf - Connection string Server:'+ @@SERVERNAME + ',Database:' + DB_NAME() +' is not an Azure Arc-enabled Managed Instance. Check the database_type parameter in the telegraf configuration.';
RAISERROR (@ErrorMessage,11,1)
RETURN
END
SELECT
'sqlserver_waitstats' AS [measurement]
,REPLACE(@@SERVERNAME,'\',':') AS [sql_instance]
,ws.[wait_type]
,[wait_time_ms]
,[wait_time_ms] - [signal_wait_time_ms] AS [resource_wait_ms]
,[signal_wait_time_ms]
,[max_wait_time_ms]
,[waiting_tasks_count]
,CASE
WHEN ws.[wait_type] LIKE 'SOS_SCHEDULER_YIELD' then 'CPU'
WHEN ws.[wait_type] = 'THREADPOOL' THEN 'Worker Thread'
WHEN ws.[wait_type] LIKE 'LCK[_]%' THEN 'Lock'
WHEN ws.[wait_type] LIKE 'LATCH[_]%' THEN 'Latch'
WHEN ws.[wait_type] LIKE 'PAGELATCH[_]%' THEN 'Buffer Latch'
WHEN ws.[wait_type] LIKE 'PAGEIOLATCH[_]%' THEN 'Buffer IO'
WHEN ws.[wait_type] LIKE 'RESOURCE_SEMAPHORE_QUERY_COMPILE%' THEN 'Compilation'
WHEN ws.[wait_type] LIKE 'CLR[_]%' or ws.[wait_type] like 'SQLCLR%' THEN 'SQL CLR'
WHEN ws.[wait_type] LIKE 'DBMIRROR_%' THEN 'Mirroring'
WHEN ws.[wait_type] LIKE 'DTC[_]%' or ws.[wait_type] LIKE 'DTCNEW%' or ws.[wait_type] LIKE 'TRAN_%'
or ws.[wait_type] LIKE 'XACT%' or ws.[wait_type] like 'MSQL_XACT%' THEN 'Transaction'
WHEN ws.[wait_type] LIKE 'SLEEP[_]%'
or ws.[wait_type] IN (
'LAZYWRITER_SLEEP', 'SQLTRACE_BUFFER_FLUSH', 'SQLTRACE_INCREMENTAL_FLUSH_SLEEP',
'SQLTRACE_WAIT_ENTRIES', 'FT_IFTS_SCHEDULER_IDLE_WAIT', 'XE_DISPATCHER_WAIT',
'REQUEST_FOR_DEADLOCK_SEARCH', 'LOGMGR_QUEUE', 'ONDEMAND_TASK_QUEUE',
'CHECKPOINT_QUEUE', 'XE_TIMER_EVENT') THEN 'Idle'
WHEN ws.[wait_type] IN(
'ASYNC_IO_COMPLETION','BACKUPIO','CHKPT','WRITE_COMPLETION',
'IO_QUEUE_LIMIT', 'IO_RETRY') THEN 'Other Disk IO'
WHEN ws.[wait_type] LIKE 'PREEMPTIVE_%' THEN 'Preemptive'
WHEN ws.[wait_type] LIKE 'BROKER[_]%' THEN 'Service Broker'
WHEN ws.[wait_type] IN (
'WRITELOG','LOGBUFFER','LOGMGR_RESERVE_APPEND',
'LOGMGR_FLUSH', 'LOGMGR_PMM_LOG') THEN 'Tran Log IO'
WHEN ws.[wait_type] LIKE 'LOG_RATE%' then 'Log Rate Governor'
WHEN ws.[wait_type] LIKE 'HADR_THROTTLE[_]%'
or ws.[wait_type] = 'THROTTLE_LOG_RATE_LOG_STORAGE' THEN 'HADR Log Rate Governor'
WHEN ws.[wait_type] LIKE 'RBIO_RG%' or ws.[wait_type] like 'WAIT_RBIO_RG%' then 'VLDB Log Rate Governor'
WHEN ws.[wait_type] LIKE 'RBIO[_]%' or ws.[wait_type] like 'WAIT_RBIO[_]%' then 'VLDB RBIO'
WHEN ws.[wait_type] IN(
'ASYNC_NETWORK_IO','EXTERNAL_SCRIPT_NETWORK_IOF',
'NET_WAITFOR_PACKET','PROXY_NETWORK_IO') THEN 'Network IO'
WHEN ws.[wait_type] IN ( 'CXPACKET', 'CXCONSUMER')
or ws.[wait_type] like 'HT%' or ws.[wait_type] like 'BMP%'
or ws.[wait_type] like 'BP%' THEN 'Parallelism'
WHEN ws.[wait_type] IN(
'CMEMTHREAD','CMEMPARTITIONED','EE_PMOLOCK','EXCHANGE',
'RESOURCE_SEMAPHORE','MEMORY_ALLOCATION_EXT',
'RESERVED_MEMORY_ALLOCATION_EXT', 'MEMORY_GRANT_UPDATE') THEN 'Memory'
WHEN ws.[wait_type] IN ('WAITFOR','WAIT_FOR_RESULTS') THEN 'User Wait'
WHEN ws.[wait_type] LIKE 'HADR[_]%' or ws.[wait_type] LIKE 'PWAIT_HADR%'
or ws.[wait_type] LIKE 'REPLICA[_]%' or ws.[wait_type] LIKE 'REPL_%'
or ws.[wait_type] LIKE 'SE_REPL[_]%'
or ws.[wait_type] LIKE 'FCB_REPLICA%' THEN 'Replication'
WHEN ws.[wait_type] LIKE 'SQLTRACE[_]%'
or ws.[wait_type] IN (
'TRACEWRITE', 'SQLTRACE_LOCK', 'SQLTRACE_FILE_BUFFER', 'SQLTRACE_FILE_WRITE_IO_COMPLETION',
'SQLTRACE_FILE_READ_IO_COMPLETION', 'SQLTRACE_PENDING_BUFFER_WRITERS', 'SQLTRACE_SHUTDOWN',
'QUERY_TRACEOUT', 'TRACE_EVTNOTIF') THEN 'Tracing'
WHEN ws.[wait_type] IN (
'FT_RESTART_CRAWL', 'FULLTEXT GATHERER', 'MSSEARCH', 'FT_METADATA_MUTEX',
'FT_IFTSHC_MUTEX', 'FT_IFTSISM_MUTEX', 'FT_IFTS_RWLOCK', 'FT_COMPROWSET_RWLOCK',
'FT_MASTER_MERGE', 'FT_PROPERTYLIST_CACHE', 'FT_MASTER_MERGE_COORDINATOR',
'PWAIT_RESOURCE_SEMAPHORE_FT_PARALLEL_QUERY_SYNC') THEN 'Full Text Search'
ELSE 'Other'
END as [wait_category]
,DATABASEPROPERTYEX(DB_NAME(), 'Updateability') as replica_updateability
FROM sys.dm_os_wait_stats AS ws WITH (NOLOCK)
WHERE
ws.[wait_type] NOT IN (
N'BROKER_EVENTHANDLER', N'BROKER_RECEIVE_WAITFOR', N'BROKER_TASK_STOP',
N'BROKER_TO_FLUSH', N'BROKER_TRANSMITTER', N'CHECKPOINT_QUEUE',
N'CHKPT', N'CLR_AUTO_EVENT', N'CLR_MANUAL_EVENT', N'CLR_SEMAPHORE',
N'DBMIRROR_DBM_EVENT', N'DBMIRROR_EVENTS_QUEUE', N'DBMIRROR_QUEUE',
N'DBMIRRORING_CMD', N'DIRTY_PAGE_POLL', N'DISPATCHER_QUEUE_SEMAPHORE',
N'EXECSYNC', N'FSAGENT', N'FT_IFTS_SCHEDULER_IDLE_WAIT', N'FT_IFTSHC_MUTEX',
N'HADR_CLUSAPI_CALL', N'HADR_FILESTREAM_IOMGR_IOCOMPLETION', N'HADR_LOGCAPTURE_WAIT',
N'HADR_NOTIFICATION_DEQUEUE', N'HADR_TIMER_TASK', N'HADR_WORK_QUEUE',
N'KSOURCE_WAKEUP', N'LAZYWRITER_SLEEP', N'LOGMGR_QUEUE',
N'MEMORY_ALLOCATION_EXT', N'ONDEMAND_TASK_QUEUE',
N'PARALLEL_REDO_WORKER_WAIT_WORK',
N'PREEMPTIVE_HADR_LEASE_MECHANISM', N'PREEMPTIVE_SP_SERVER_DIAGNOSTICS',
N'PREEMPTIVE_OS_LIBRARYOPS', N'PREEMPTIVE_OS_COMOPS', N'PREEMPTIVE_OS_CRYPTOPS',
N'PREEMPTIVE_OS_PIPEOPS','PREEMPTIVE_OS_GENERICOPS', N'PREEMPTIVE_OS_VERIFYTRUST',
N'PREEMPTIVE_OS_DEVICEOPS',
N'PREEMPTIVE_XE_CALLBACKEXECUTE', N'PREEMPTIVE_XE_DISPATCHER',
N'PREEMPTIVE_XE_GETTARGETSTATE', N'PREEMPTIVE_XE_SESSIONCOMMIT',
N'PREEMPTIVE_XE_TARGETINIT', N'PREEMPTIVE_XE_TARGETFINALIZE',
N'PWAIT_ALL_COMPONENTS_INITIALIZED', N'PWAIT_DIRECTLOGCONSUMER_GETNEXT',
N'QDS_PERSIST_TASK_MAIN_LOOP_SLEEP',
N'QDS_ASYNC_QUEUE',
N'QDS_CLEANUP_STALE_QUERIES_TASK_MAIN_LOOP_SLEEP', N'REQUEST_FOR_DEADLOCK_SEARCH',
N'RESOURCE_QUEUE', N'SERVER_IDLE_CHECK', N'SLEEP_BPOOL_FLUSH', N'SLEEP_DBSTARTUP',
N'SLEEP_DCOMSTARTUP', N'SLEEP_MASTERDBREADY', N'SLEEP_MASTERMDREADY',
N'SLEEP_MASTERUPGRADED', N'SLEEP_MSDBSTARTUP', N'SLEEP_SYSTEMTASK', N'SLEEP_TASK',
N'SLEEP_TEMPDBSTARTUP', N'SNI_HTTP_ACCEPT', N'SP_SERVER_DIAGNOSTICS_SLEEP',
N'SQLTRACE_BUFFER_FLUSH', N'SQLTRACE_INCREMENTAL_FLUSH_SLEEP',
N'SQLTRACE_WAIT_ENTRIES',
N'WAIT_FOR_RESULTS', N'WAITFOR', N'WAITFOR_TASKSHUTDOWN', N'WAIT_XTP_HOST_WAIT',
N'WAIT_XTP_OFFLINE_CKPT_NEW_LOG', N'WAIT_XTP_CKPT_CLOSE',
N'XE_BUFFERMGR_ALLPROCESSED_EVENT', N'XE_DISPATCHER_JOIN',
N'XE_DISPATCHER_WAIT', N'XE_LIVE_TARGET_TVF', N'XE_TIMER_EVENT',
N'SOS_WORK_DISPATCHER','RESERVED_MEMORY_ALLOCATION_EXT','SQLTRACE_WAIT_ENTRIES',
N'RBIO_COMM_RETRY')
AND [waiting_tasks_count] > 10
AND [wait_time_ms] > 100;
`
const sqlAzureArcMIPerformanceCounters = `
SET DEADLOCK_PRIORITY -10;
IF SERVERPROPERTY('EngineEdition') <> 10 BEGIN /*not Azure Arc-enabled Managed Instance*/
DECLARE @ErrorMessage AS nvarchar(500) = 'Telegraf - Connection string Server:'+ @@SERVERNAME + ',Database:' + DB_NAME() +' is not an Azure Arc-enabled Managed Instance. Check the database_type parameter in the telegraf configuration.';
RAISERROR (@ErrorMessage,11,1)
RETURN
END
DECLARE @PCounters TABLE
(
[object_name] nvarchar(128),
[counter_name] nvarchar(128),
[instance_name] nvarchar(128),
[cntr_value] bigint,
[cntr_type] INT ,
Primary Key([object_name],[counter_name],[instance_name])
);
WITH PerfCounters AS (
SELECT DISTINCT
RTrim(spi.[object_name]) [object_name]
,RTrim(spi.[counter_name]) [counter_name]
,CASE WHEN (
RTRIM(spi.[object_name]) LIKE '%:Databases'
OR RTRIM(spi.[object_name]) LIKE '%:Database Replica'
OR RTRIM(spi.[object_name]) LIKE '%:Catalog Metadata'
OR RTRIM(spi.[object_name]) LIKE '%:Query Store'
OR RTRIM(spi.[object_name]) LIKE '%:Columnstore'
OR RTRIM(spi.[object_name]) LIKE '%:Advanced Analytics')
AND TRY_CONVERT([uniqueidentifier], spi.[instance_name]) IS NOT NULL -- for cloud only
THEN ISNULL(d.[name],RTRIM(spi.instance_name)) -- Elastic Pools counters exist for all databases but sys.databases only has current DB value
WHEN
RTRIM([object_name]) LIKE '%:Availability Replica'
AND TRY_CONVERT([uniqueidentifier], spi.[instance_name]) IS NOT NULL -- for cloud only
THEN ISNULL(d.[name],RTRIM(spi.[instance_name])) + RTRIM(SUBSTRING(spi.[instance_name], 37, LEN(spi.[instance_name])))
ELSE RTRIM(spi.instance_name)
END AS [instance_name]
,CAST(spi.[cntr_value] AS BIGINT) AS [cntr_value]
,spi.[cntr_type]
FROM sys.dm_os_performance_counters AS spi
LEFT JOIN sys.databases AS d
ON LEFT(spi.[instance_name], 36) -- some instance_name values have an additional identifier appended after the GUID
= CASE
/*in SQL DB standalone, physical_database_name for master is the GUID of the user database*/
WHEN d.[name] = 'master' AND TRY_CONVERT([uniqueidentifier], d.[physical_database_name]) IS NOT NULL
THEN d.[name]
ELSE d.[physical_database_name]
END
WHERE
counter_name IN (
'SQL Compilations/sec'
,'SQL Re-Compilations/sec'
,'User Connections'
,'Batch Requests/sec'
,'Logouts/sec'
,'Logins/sec'
,'Processes blocked'
,'Latch Waits/sec'
,'Full Scans/sec'
,'Index Searches/sec'
,'Page Splits/sec'
,'Page lookups/sec'
,'Page reads/sec'
,'Page writes/sec'
,'Readahead pages/sec'
,'Lazy writes/sec'
,'Checkpoint pages/sec'
,'Table Lock Escalations/sec'
,'Page life expectancy'
,'Log File(s) Size (KB)'
,'Log File(s) Used Size (KB)'
,'Data File(s) Size (KB)'
,'Transactions/sec'
,'Write Transactions/sec'
,'Active Transactions'
,'Log Growths'
,'Active Temp Tables'
,'Logical Connections'
,'Temp Tables Creation Rate'
,'Temp Tables For Destruction'
,'Free Space in tempdb (KB)'
,'Version Store Size (KB)'
,'Memory Grants Pending'
,'Memory Grants Outstanding'
,'Free list stalls/sec'
,'Buffer cache hit ratio'
,'Buffer cache hit ratio base'
,'Backup/Restore Throughput/sec'
,'Total Server Memory (KB)'
,'Target Server Memory (KB)'
,'Log Flushes/sec'
,'Log Flush Wait Time'
,'Memory broker clerk size'
,'Log Bytes Flushed/sec'
,'Bytes Sent to Replica/sec'
,'Log Send Queue'
,'Bytes Sent to Transport/sec'
,'Sends to Replica/sec'
,'Bytes Sent to Transport/sec'
,'Sends to Transport/sec'
,'Bytes Received from Replica/sec'
,'Receives from Replica/sec'
,'Flow Control Time (ms/sec)'
,'Flow Control/sec'
,'Resent Messages/sec'
,'Redone Bytes/sec'
,'XTP Memory Used (KB)'
,'Transaction Delay'
,'Log Bytes Received/sec'
,'Log Apply Pending Queue'
,'Redone Bytes/sec'
,'Recovery Queue'
,'Log Apply Ready Queue'
,'CPU usage %'
,'CPU usage % base'
,'Queued requests'
,'Requests completed/sec'
,'Blocked tasks'
,'Active memory grant amount (KB)'
,'Disk Read Bytes/sec'
,'Disk Read IO Throttled/sec'
,'Disk Read IO/sec'
,'Disk Write Bytes/sec'
,'Disk Write IO Throttled/sec'
,'Disk Write IO/sec'
,'Used memory (KB)'
,'Forwarded Records/sec'
,'Background Writer pages/sec'
,'Percent Log Used'
,'Log Send Queue KB'
,'Redo Queue KB'
,'Mirrored Write Transactions/sec'
,'Group Commit Time'
,'Group Commits/Sec'
,'Workfiles Created/sec'
,'Worktables Created/sec'
,'Distributed Query'
,'DTC calls'
,'Query Store CPU usage'
) OR (
spi.[object_name] LIKE '%User Settable%'
OR spi.[object_name] LIKE '%SQL Errors%'
OR spi.[object_name] LIKE '%Batch Resp Statistics%'
) OR (
spi.[instance_name] IN ('_Total')
AND spi.[counter_name] IN (
'Lock Timeouts/sec'
,'Lock Timeouts (timeout > 0)/sec'
,'Number of Deadlocks/sec'
,'Lock Waits/sec'
,'Latch Waits/sec'
)
)
)
INSERT INTO @PCounters select * from PerfCounters
SELECT
'sqlserver_performance' AS [measurement]
,REPLACE(@@SERVERNAME,'\',':') AS [sql_instance]
,pc.[object_name] AS [object]
,pc.[counter_name] AS [counter]
,CASE pc.[instance_name]
WHEN '_Total' THEN 'Total'
ELSE ISNULL(pc.[instance_name],'')
END AS [instance]
,CAST(CASE WHEN pc.[cntr_type] = 537003264 AND pc1.[cntr_value] > 0 THEN (pc.[cntr_value] * 1.0) / (pc1.[cntr_value] * 1.0) * 100 ELSE pc.[cntr_value] END AS float(10)) AS [value]
,cast(pc.[cntr_type] as varchar(25)) as [counter_type]
,DATABASEPROPERTYEX(DB_NAME(), 'Updateability') as replica_updateability
from @PCounters pc
LEFT OUTER JOIN @PCounters AS pc1
ON (
pc.[counter_name] = REPLACE(pc1.[counter_name],' base','')
OR pc.[counter_name] = REPLACE(pc1.[counter_name],' base',' (ms)')
)
AND pc.[object_name] = pc1.[object_name]
AND pc.[instance_name] = pc1.[instance_name]
AND pc1.[counter_name] LIKE '%base'
WHERE
pc.[counter_name] NOT LIKE '% base'
OPTION (RECOMPILE);
`
const sqlAzureArcMIRequests string = `
IF SERVERPROPERTY('EngineEdition') <> 10 BEGIN /*not Azure Arc-enabled Managed Instance*/
DECLARE @ErrorMessage AS nvarchar(500) = 'Telegraf - Connection string Server:'+ @@SERVERNAME + ',Database:' + DB_NAME() +' is not an Azure Arc-enabled Managed Instance. Check the database_type parameter in the telegraf configuration.';
RAISERROR (@ErrorMessage,11,1)
RETURN
END
BEGIN TRY
SELECT
[measurement],[sql_instance],[database_name],[session_id]
,ISNULL([request_id],0) AS [request_id]
,[blocking_session_id],[status],[cpu_time_ms]
,[total_elapsed_time_ms],[logical_reads],[writes]
,[command],[wait_time_ms],[wait_type]
,[wait_resource],[program_name]
,[host_name],[nt_user_name],[login_name]
,[transaction_isolation_level],[granted_query_memory_pages],[percent_complete]
,[statement_text],[objectid],[stmt_object_name]
,[stmt_db_name],[query_hash],[query_plan_hash]
,replica_updateability
,[session_db_name],[open_transaction]
FROM (
SELECT
'sqlserver_requests' AS [measurement]
,REPLACE(@@SERVERNAME,'\',':') AS [sql_instance]
,DB_NAME() as [database_name]
,s.[session_id]
,r.[request_id]
,DB_NAME(COALESCE(r.[database_id], s.[database_id])) AS [session_db_name]
,COALESCE(r.[status], s.[status]) AS [status]
,COALESCE(r.[cpu_time], s.[cpu_time]) AS [cpu_time_ms]
,COALESCE(r.[total_elapsed_time], s.[total_elapsed_time]) AS [total_elapsed_time_ms]
,COALESCE(r.[logical_reads], s.[logical_reads]) AS [logical_reads]
,COALESCE(r.[writes], s.[writes]) AS [writes]
,r.[command]
,r.[wait_time] AS [wait_time_ms]
,r.[wait_type]
,r.[wait_resource]
,NULLIF(r.[blocking_session_id],0) AS [blocking_session_id]
,s.[program_name]
,s.[host_name]
,s.[nt_user_name]
,s.[login_name]
,COALESCE(r.[open_transaction_count], s.[open_transaction_count]) AS [open_transaction]
,LEFT (CASE COALESCE(r.[transaction_isolation_level], s.[transaction_isolation_level])
WHEN 0 THEN '0-Read Committed'
WHEN 1 THEN '1-Read Uncommitted (NOLOCK)'
WHEN 2 THEN '2-Read Committed'
WHEN 3 THEN '3-Repeatable Read'
WHEN 4 THEN '4-Serializable'
WHEN 5 THEN '5-Snapshot'
ELSE CONVERT (varchar(30), r.[transaction_isolation_level]) + '-UNKNOWN'
END, 30) AS [transaction_isolation_level]
,r.[granted_query_memory] AS [granted_query_memory_pages]
,r.[percent_complete]
,SUBSTRING(
qt.[text],
r.[statement_start_offset] / 2 + 1,
(CASE WHEN r.[statement_end_offset] = -1
THEN DATALENGTH(qt.[text])
ELSE r.[statement_end_offset]
END - r.[statement_start_offset]) / 2 + 1
) AS [statement_text]
,qt.[objectid]
,QUOTENAME(OBJECT_SCHEMA_NAME(qt.[objectid], qt.[dbid])) + '.' + QUOTENAME(OBJECT_NAME(qt.[objectid], qt.[dbid])) as [stmt_object_name]
,DB_NAME(qt.[dbid]) AS [stmt_db_name]
,CONVERT(varchar(20),r.[query_hash],1) AS [query_hash]
,CONVERT(varchar(20),r.[query_plan_hash],1) AS [query_plan_hash]
,DATABASEPROPERTYEX(DB_NAME(), 'Updateability') as replica_updateability
,s.[is_user_process]
,[blocking_or_blocked] = COUNT(*) OVER(PARTITION BY ISNULL(NULLIF(r.[blocking_session_id], 0),s.[session_id]))
FROM sys.dm_exec_sessions AS s
LEFT OUTER JOIN sys.dm_exec_requests AS r
ON s.[session_id] = r.[session_id]
OUTER APPLY sys.dm_exec_sql_text(r.[sql_handle]) AS qt
) AS data
WHERE
[blocking_or_blocked] > 1 --Always include blocking or blocked sessions/requests
OR [open_transaction] >= 1 --Always include sessions with open transactions
OR (
[request_id] IS NOT NULL --A request must exists
AND ( --Always fetch user process (in any state), fetch system process only if active
[is_user_process] = 1
OR [status] COLLATE Latin1_General_BIN NOT IN ('background', 'sleeping')
)
AND [session_id] <> @@SPID
)
OPTION(MAXDOP 1);
END TRY
BEGIN CATCH
IF (ERROR_NUMBER() <> 976) --Avoid possible errors from secondary replica
THROW;
END CATCH
`
const sqlAzureArcMISchedulers string = `
IF SERVERPROPERTY('EngineEdition') <> 10 BEGIN /*not Azure Arc-enabled Managed Instance*/
DECLARE @ErrorMessage AS nvarchar(500) = 'Telegraf - Connection string Server:'+ @@SERVERNAME + ',Database:' + DB_NAME() +' is not an Azure Arc-enabled Managed Instance. Check the database_type parameter in the telegraf configuration.';
RAISERROR (@ErrorMessage,11,1)
RETURN
END
SELECT
'sqlserver_schedulers' AS [measurement]
,REPLACE(@@SERVERNAME, '\', ':') AS [sql_instance]
,CAST(s.[scheduler_id] AS VARCHAR(4)) AS [scheduler_id]
,CAST(s.[cpu_id] AS VARCHAR(4)) AS [cpu_id]
,s.[is_online]
,s.[is_idle]
,s.[preemptive_switches_count]
,s.[context_switches_count]
,s.[current_tasks_count]
,s.[runnable_tasks_count]
,s.[current_workers_count]
,s.[active_workers_count]
,s.[work_queue_count]
,s.[pending_disk_io_count]
,s.[load_factor]
,s.[yield_count]
,s.[total_cpu_usage_ms]
,s.[total_scheduler_delay_ms]
,DATABASEPROPERTYEX(DB_NAME(), 'Updateability') as replica_updateability
FROM sys.dm_os_schedulers AS s
`

View file

@ -0,0 +1,313 @@
package sqlserver
import (
"os"
"testing"
"github.com/stretchr/testify/require"
"github.com/influxdata/telegraf/config"
"github.com/influxdata/telegraf/testutil"
)
func TestAzureSQLIntegration_ArcManaged_DatabaseIO_Query(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
if os.Getenv("AZURESQL_ARCMI_CONNECTION_STRING") == "" {
t.Skip("Missing environment variable AZURESQL_ARCMI_CONNECTION_STRING")
}
connectionString := os.Getenv("AZURESQL_ARCMI_CONNECTION_STRING")
sl := config.NewSecret([]byte(connectionString))
server := &SQLServer{
Servers: []*config.Secret{&sl},
IncludeQuery: []string{"AzureArcSQLMIDatabaseIO"},
AuthMethod: "connection_string",
DatabaseType: "AzureArcSQLManagedInstance",
}
var acc testutil.Accumulator
require.NoError(t, server.Start(&acc))
require.NoError(t, server.Gather(&acc))
require.True(t, acc.HasMeasurement("sqlserver_database_io"))
require.True(t, acc.HasTag("sqlserver_database_io", "sql_instance"))
require.True(t, acc.HasTag("sqlserver_database_io", "database_name"))
require.True(t, acc.HasTag("sqlserver_database_io", "physical_filename"))
require.True(t, acc.HasTag("sqlserver_database_io", "logical_filename"))
require.True(t, acc.HasTag("sqlserver_database_io", "file_type"))
require.True(t, acc.HasInt64Field("sqlserver_database_io", "reads"))
require.True(t, acc.HasInt64Field("sqlserver_database_io", "read_bytes"))
require.True(t, acc.HasInt64Field("sqlserver_database_io", "read_latency_ms"))
require.True(t, acc.HasInt64Field("sqlserver_database_io", "write_latency_ms"))
require.True(t, acc.HasInt64Field("sqlserver_database_io", "writes"))
require.True(t, acc.HasInt64Field("sqlserver_database_io", "write_bytes"))
require.True(t, acc.HasInt64Field("sqlserver_database_io", "rg_read_stall_ms"))
require.True(t, acc.HasInt64Field("sqlserver_database_io", "rg_write_stall_ms"))
require.True(t, acc.HasTag("sqlserver_database_io", "replica_updateability"))
server.Stop()
}
func TestAzureSQLIntegration_ArcManaged_ServerProperties_Query(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
if os.Getenv("AZURESQL_ARCMI_CONNECTION_STRING") == "" {
t.Skip("Missing environment variable AZURESQL_ARCMI_CONNECTION_STRING")
}
connectionString := os.Getenv("AZURESQL_ARCMI_CONNECTION_STRING")
sl := config.NewSecret([]byte(connectionString))
server := &SQLServer{
Servers: []*config.Secret{&sl},
IncludeQuery: []string{"AzureArcSQLMIServerProperties"},
AuthMethod: "connection_string",
DatabaseType: "AzureArcSQLManagedInstance",
}
var acc testutil.Accumulator
require.NoError(t, server.Start(&acc))
require.NoError(t, server.Gather(&acc))
require.True(t, acc.HasMeasurement("sqlserver_server_properties"))
require.True(t, acc.HasTag("sqlserver_server_properties", "sql_instance"))
require.True(t, acc.HasInt64Field("sqlserver_server_properties", "cpu_count"))
require.True(t, acc.HasInt64Field("sqlserver_server_properties", "server_memory"))
require.True(t, acc.HasTag("sqlserver_server_properties", "sku"))
require.True(t, acc.HasInt64Field("sqlserver_server_properties", "engine_edition"))
require.True(t, acc.HasTag("sqlserver_server_properties", "hardware_type"))
require.True(t, acc.HasField("sqlserver_server_properties", "uptime")) // Time field.
require.True(t, acc.HasTag("sqlserver_server_properties", "sql_version"))
require.True(t, acc.HasTag("sqlserver_server_properties", "sql_version_desc"))
require.True(t, acc.HasInt64Field("sqlserver_server_properties", "db_online"))
require.True(t, acc.HasInt64Field("sqlserver_server_properties", "db_restoring"))
require.True(t, acc.HasInt64Field("sqlserver_server_properties", "db_recovering"))
require.True(t, acc.HasInt64Field("sqlserver_server_properties", "db_recoveryPending"))
require.True(t, acc.HasInt64Field("sqlserver_server_properties", "db_suspect"))
require.True(t, acc.HasTag("sqlserver_server_properties", "replica_updateability"))
// This query should only return one row
require.Len(t, acc.Metrics, 1)
server.Stop()
}
func TestAzureSQLIntegration_ArcManaged_OsWaitStats_Query(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
if os.Getenv("AZURESQL_ARCMI_CONNECTION_STRING") == "" {
t.Skip("Missing environment variable AZURESQL_ARCMI_CONNECTION_STRING")
}
connectionString := os.Getenv("AZURESQL_ARCMI_CONNECTION_STRING")
sl := config.NewSecret([]byte(connectionString))
server := &SQLServer{
Servers: []*config.Secret{&sl},
IncludeQuery: []string{"AzureArcSQLMIOsWaitstats"},
AuthMethod: "connection_string",
DatabaseType: "AzureArcSQLManagedInstance",
}
var acc testutil.Accumulator
require.NoError(t, server.Start(&acc))
require.NoError(t, server.Gather(&acc))
require.True(t, acc.HasMeasurement("sqlserver_waitstats"))
require.True(t, acc.HasTag("sqlserver_waitstats", "sql_instance"))
require.True(t, acc.HasTag("sqlserver_waitstats", "wait_type"))
require.True(t, acc.HasInt64Field("sqlserver_waitstats", "waiting_tasks_count"))
require.True(t, acc.HasInt64Field("sqlserver_waitstats", "wait_time_ms"))
require.True(t, acc.HasInt64Field("sqlserver_waitstats", "max_wait_time_ms"))
require.True(t, acc.HasInt64Field("sqlserver_waitstats", "signal_wait_time_ms"))
require.True(t, acc.HasInt64Field("sqlserver_waitstats", "resource_wait_ms"))
require.True(t, acc.HasTag("sqlserver_waitstats", "wait_category"))
require.True(t, acc.HasTag("sqlserver_waitstats", "replica_updateability"))
server.Stop()
}
func TestAzureSQLIntegration_ArcManaged_MemoryClerks_Query(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
if os.Getenv("AZURESQL_ARCMI_CONNECTION_STRING") == "" {
t.Skip("Missing environment variable AZURESQL_ARCMI_CONNECTION_STRING")
}
connectionString := os.Getenv("AZURESQL_ARCMI_CONNECTION_STRING")
sl := config.NewSecret([]byte(connectionString))
server := &SQLServer{
Servers: []*config.Secret{&sl},
IncludeQuery: []string{"AzureArcSQLMIMemoryClerks"},
AuthMethod: "connection_string",
DatabaseType: "AzureArcSQLManagedInstance",
}
var acc testutil.Accumulator
require.NoError(t, server.Start(&acc))
require.NoError(t, server.Gather(&acc))
require.True(t, acc.HasMeasurement("sqlserver_memory_clerks"))
require.True(t, acc.HasTag("sqlserver_memory_clerks", "sql_instance"))
require.True(t, acc.HasTag("sqlserver_memory_clerks", "clerk_type"))
require.True(t, acc.HasInt64Field("sqlserver_memory_clerks", "size_kb"))
require.True(t, acc.HasTag("sqlserver_memory_clerks", "replica_updateability"))
server.Stop()
}
func TestAzureSQLIntegration_ArcManaged_PerformanceCounters_Query(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
if os.Getenv("AZURESQL_ARCMI_CONNECTION_STRING") == "" {
t.Skip("Missing environment variable AZURESQL_ARCMI_CONNECTION_STRING")
}
connectionString := os.Getenv("AZURESQL_ARCMI_CONNECTION_STRING")
sl := config.NewSecret([]byte(connectionString))
server := &SQLServer{
Servers: []*config.Secret{&sl},
IncludeQuery: []string{"AzureArcSQLMIPerformanceCounters"},
AuthMethod: "connection_string",
DatabaseType: "AzureArcSQLManagedInstance",
}
var acc testutil.Accumulator
require.NoError(t, server.Start(&acc))
require.NoError(t, server.Gather(&acc))
require.True(t, acc.HasMeasurement("sqlserver_performance"))
require.True(t, acc.HasTag("sqlserver_performance", "sql_instance"))
require.True(t, acc.HasTag("sqlserver_performance", "object"))
require.True(t, acc.HasTag("sqlserver_performance", "counter"))
require.True(t, acc.HasTag("sqlserver_performance", "instance"))
require.True(t, acc.HasFloatField("sqlserver_performance", "value"))
require.True(t, acc.HasTag("sqlserver_performance", "counter_type"))
require.True(t, acc.HasTag("sqlserver_performance", "replica_updateability"))
server.Stop()
}
func TestAzureSQLIntegration_ArcManaged_Requests_Query(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
if os.Getenv("AZURESQL_ARCMI_CONNECTION_STRING") == "" {
t.Skip("Missing environment variable AZURESQL_ARCMI_CONNECTION_STRING")
}
connectionString := os.Getenv("AZURESQL_ARCMI_CONNECTION_STRING")
sl := config.NewSecret([]byte(connectionString))
server := &SQLServer{
Servers: []*config.Secret{&sl},
IncludeQuery: []string{"AzureArcSQLMIRequests"},
AuthMethod: "connection_string",
DatabaseType: "AzureArcSQLManagedInstance",
}
var acc testutil.Accumulator
require.NoError(t, server.Start(&acc))
require.NoError(t, server.Gather(&acc))
require.True(t, acc.HasMeasurement("sqlserver_requests"))
require.True(t, acc.HasTag("sqlserver_requests", "sql_instance"))
require.True(t, acc.HasTag("sqlserver_requests", "database_name"))
require.True(t, acc.HasInt64Field("sqlserver_requests", "session_id"))
require.True(t, acc.HasInt64Field("sqlserver_requests", "request_id"))
require.True(t, acc.HasTag("sqlserver_requests", "status"))
require.True(t, acc.HasInt64Field("sqlserver_requests", "cpu_time_ms"))
require.True(t, acc.HasInt64Field("sqlserver_requests", "total_elapsed_time_ms"))
require.True(t, acc.HasInt64Field("sqlserver_requests", "logical_reads"))
require.True(t, acc.HasInt64Field("sqlserver_requests", "writes"))
require.True(t, acc.HasTag("sqlserver_requests", "command"))
require.True(t, acc.HasInt64Field("sqlserver_requests", "wait_time_ms"))
require.True(t, acc.HasTag("sqlserver_requests", "wait_type"))
require.True(t, acc.HasTag("sqlserver_requests", "wait_resource"))
require.True(t, acc.HasInt64Field("sqlserver_requests", "blocking_session_id"))
require.True(t, acc.HasTag("sqlserver_requests", "program_name"))
require.True(t, acc.HasTag("sqlserver_requests", "host_name"))
require.True(t, acc.HasTag("sqlserver_requests", "nt_user_name"))
require.True(t, acc.HasTag("sqlserver_requests", "login_name"))
require.True(t, acc.HasInt64Field("sqlserver_requests", "open_transaction"))
require.True(t, acc.HasTag("sqlserver_requests", "transaction_isolation_level"))
require.True(t, acc.HasInt64Field("sqlserver_requests", "granted_query_memory_pages"))
require.True(t, acc.HasFloatField("sqlserver_requests", "percent_complete"))
require.True(t, acc.HasTag("sqlserver_requests", "statement_text"))
require.True(t, acc.HasField("sqlserver_requests", "objectid")) // Can be null.
require.True(t, acc.HasField("sqlserver_requests", "stmt_object_name")) // Can be null.
require.True(t, acc.HasField("sqlserver_requests", "stmt_db_name")) // Can be null.
require.True(t, acc.HasTag("sqlserver_requests", "query_hash"))
require.True(t, acc.HasTag("sqlserver_requests", "query_plan_hash"))
require.True(t, acc.HasTag("sqlserver_requests", "session_db_name"))
require.True(t, acc.HasTag("sqlserver_requests", "replica_updateability"))
server.Stop()
}
func TestAzureSQLIntegration_ArcManaged_Schedulers_Query(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
if os.Getenv("AZURESQL_ARCMI_CONNECTION_STRING") == "" {
t.Skip("Missing environment variable AZURESQL_ARCMI_CONNECTION_STRING")
}
connectionString := os.Getenv("AZURESQL_ARCMI_CONNECTION_STRING")
sl := config.NewSecret([]byte(connectionString))
server := &SQLServer{
Servers: []*config.Secret{&sl},
IncludeQuery: []string{"AzureArcSQLMISchedulers"},
AuthMethod: "connection_string",
DatabaseType: "AzureArcSQLManagedInstance",
}
var acc testutil.Accumulator
require.NoError(t, server.Start(&acc))
require.NoError(t, server.Gather(&acc))
require.True(t, acc.HasMeasurement("sqlserver_schedulers"))
require.True(t, acc.HasTag("sqlserver_schedulers", "sql_instance"))
require.True(t, acc.HasTag("sqlserver_schedulers", "scheduler_id"))
require.True(t, acc.HasTag("sqlserver_schedulers", "cpu_id"))
require.True(t, acc.HasField("sqlserver_schedulers", "is_online")) // Bool field.
require.True(t, acc.HasField("sqlserver_schedulers", "is_idle")) // Bool field.
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "preemptive_switches_count"))
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "context_switches_count"))
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "current_tasks_count"))
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "runnable_tasks_count"))
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "current_workers_count"))
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "active_workers_count"))
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "work_queue_count"))
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "pending_disk_io_count"))
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "load_factor"))
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "yield_count"))
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "total_cpu_usage_ms"))
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "total_scheduler_delay_ms"))
require.True(t, acc.HasTag("sqlserver_schedulers", "replica_updateability"))
server.Stop()
}

View file

@ -0,0 +1,702 @@
//nolint:lll // conditionally long lines allowed
package sqlserver
import (
_ "github.com/microsoft/go-mssqldb" // go-mssqldb initialization
)
// ------------------------------------------------------------------------------------------------
// ------------------ Azure SQL Database ----------------------------------------------------------
// ------------------------------------------------------------------------------------------------
// Only executed if AzureDB flag is set
const sqlAzureDBResourceStats string = `
IF SERVERPROPERTY('EngineEdition') <> 5 BEGIN /*not Azure SQL DB*/
DECLARE @ErrorMessage AS nvarchar(500) = 'Telegraf - Connection string Server:'+ @@SERVERNAME + ',Database:' + DB_NAME() +' is not an Azure SQL DB. Check the database_type parameter in the telegraf configuration.';
RAISERROR (@ErrorMessage,11,1)
RETURN
END
SELECT TOP(1)
'sqlserver_azure_db_resource_stats' AS [measurement]
,REPLACE(@@SERVERNAME,'\',':') AS [sql_instance]
,DB_NAME() as [database_name]
,cast([avg_cpu_percent] as float) as [avg_cpu_percent]
,cast([avg_data_io_percent] as float) as [avg_data_io_percent]
,cast([avg_log_write_percent] as float) as [avg_log_write_percent]
,cast([avg_memory_usage_percent] as float) as [avg_memory_usage_percent]
,cast([xtp_storage_percent] as float) as [xtp_storage_percent]
,cast([max_worker_percent] as float) as [max_worker_percent]
,cast([max_session_percent] as float) as [max_session_percent]
,[dtu_limit]
,cast([avg_login_rate_percent] as float) as [avg_login_rate_percent]
,[end_time]
,cast([avg_instance_memory_percent] as float) as [avg_instance_memory_percent]
,cast([avg_instance_cpu_percent] as float) as [avg_instance_cpu_percent]
,DATABASEPROPERTYEX(DB_NAME(), 'Updateability') as replica_updateability
FROM
sys.dm_db_resource_stats WITH (NOLOCK)
ORDER BY
[end_time] DESC;
`
// Resource Governance is only relevant to Azure SQL DB into separate collector
// This will only be collected for Azure SQL Database.
const sqlAzureDBResourceGovernance string = `
IF SERVERPROPERTY('EngineEdition') <> 5 BEGIN /*not Azure SQL DB*/
DECLARE @ErrorMessage AS nvarchar(500) = 'Telegraf - Connection string Server:'+ @@SERVERNAME + ',Database:' + DB_NAME() +' is not an Azure SQL DB. Check the database_type parameter in the telegraf configuration.';
RAISERROR (@ErrorMessage,11,1)
RETURN
END
SELECT
'sqlserver_db_resource_governance' AS [measurement]
,REPLACE(@@SERVERNAME,'\',':') AS [sql_instance]
,DB_NAME() as [database_name]
,[slo_name]
,[dtu_limit]
,[max_cpu]
,[cap_cpu]
,[instance_cap_cpu]
,[max_db_memory]
,[max_db_max_size_in_mb]
,[db_file_growth_in_mb]
,[log_size_in_mb]
,[instance_max_worker_threads]
,[primary_group_max_workers]
,[instance_max_log_rate]
,[primary_min_log_rate]
,[primary_max_log_rate]
,[primary_group_min_io]
,[primary_group_max_io]
,[primary_group_min_cpu]
,[primary_group_max_cpu]
,[primary_pool_max_workers]
,[pool_max_io]
,[checkpoint_rate_mbps]
,[checkpoint_rate_io]
,[volume_local_iops]
,[volume_managed_xstore_iops]
,[volume_external_xstore_iops]
,[volume_type_local_iops]
,[volume_type_managed_xstore_iops]
,[volume_type_external_xstore_iops]
,[volume_pfs_iops]
,[volume_type_pfs_iops]
,DATABASEPROPERTYEX(DB_NAME(), 'Updateability') as replica_updateability
FROM
sys.dm_user_db_resource_governance WITH (NOLOCK);
`
// DB level wait stats that are only relevant to Azure SQL DB into separate collector
// This will only be collected for Azure SQL Database.
const sqlAzureDBWaitStats string = `
IF SERVERPROPERTY('EngineEdition') <> 5 BEGIN /*not Azure SQL DB*/
DECLARE @ErrorMessage AS nvarchar(500) = 'Telegraf - Connection string Server:'+ @@SERVERNAME + ',Database:' + DB_NAME() +' is not an Azure SQL DB. Check the database_type parameter in the telegraf configuration.';
RAISERROR (@ErrorMessage,11,1)
RETURN
END
SELECT
'sqlserver_azuredb_waitstats' AS [measurement]
,REPLACE(@@SERVERNAME,'\',':') AS [sql_instance]
,DB_NAME() as [database_name]
,dbws.[wait_type]
,dbws.[wait_time_ms]
,dbws.[wait_time_ms] - [signal_wait_time_ms] AS [resource_wait_ms]
,dbws.[signal_wait_time_ms]
,dbws.[max_wait_time_ms]
,dbws.[waiting_tasks_count]
,DATABASEPROPERTYEX(DB_NAME(), 'Updateability') as replica_updateability
FROM
sys.dm_db_wait_stats AS dbws WITH (NOLOCK)
WHERE
dbws.[wait_type] NOT IN (
N'BROKER_EVENTHANDLER', N'BROKER_RECEIVE_WAITFOR', N'BROKER_TASK_STOP',
N'BROKER_TO_FLUSH', N'BROKER_TRANSMITTER', N'CHECKPOINT_QUEUE',
N'CHKPT', N'CLR_AUTO_EVENT', N'CLR_MANUAL_EVENT', N'CLR_SEMAPHORE',
N'DBMIRROR_DBM_EVENT', N'DBMIRROR_EVENTS_QUEUE', N'DBMIRROR_WORKER_QUEUE',
N'DBMIRRORING_CMD', N'DIRTY_PAGE_POLL', N'DISPATCHER_QUEUE_SEMAPHORE',
N'EXECSYNC', N'FSAGENT', N'FT_IFTS_SCHEDULER_IDLE_WAIT', N'FT_IFTSHC_MUTEX',
N'HADR_CLUSAPI_CALL', N'HADR_FILESTREAM_IOMGR_IOCOMPLETION', N'HADR_LOGCAPTURE_WAIT',
N'HADR_NOTIFICATION_DEQUEUE', N'HADR_TIMER_TASK', N'HADR_WORK_QUEUE',
N'KSOURCE_WAKEUP', N'LAZYWRITER_SLEEP', N'LOGMGR_QUEUE',
N'MEMORY_ALLOCATION_EXT', N'ONDEMAND_TASK_QUEUE',
N'PARALLEL_REDO_WORKER_WAIT_WORK',
N'PREEMPTIVE_HADR_LEASE_MECHANISM', N'PREEMPTIVE_SP_SERVER_DIAGNOSTICS',
N'PREEMPTIVE_OS_LIBRARYOPS', N'PREEMPTIVE_OS_COMOPS', N'PREEMPTIVE_OS_CRYPTOPS',
N'PREEMPTIVE_OS_PIPEOPS','PREEMPTIVE_OS_GENERICOPS', N'PREEMPTIVE_OS_VERIFYTRUST',
N'PREEMPTIVE_OS_DEVICEOPS',
N'PREEMPTIVE_XE_CALLBACKEXECUTE', N'PREEMPTIVE_XE_DISPATCHER',
N'PREEMPTIVE_XE_GETTARGETSTATE', N'PREEMPTIVE_XE_SESSIONCOMMIT',
N'PREEMPTIVE_XE_TARGETINIT', N'PREEMPTIVE_XE_TARGETFINALIZE',
N'PWAIT_ALL_COMPONENTS_INITIALIZED', N'PWAIT_DIRECTLOGCONSUMER_GETNEXT',
N'QDS_PERSIST_TASK_MAIN_LOOP_SLEEP',
N'QDS_ASYNC_QUEUE',
N'QDS_CLEANUP_STALE_QUERIES_TASK_MAIN_LOOP_SLEEP', N'REQUEST_FOR_DEADLOCK_SEARCH',
N'RESOURCE_QUEUE', N'SERVER_IDLE_CHECK', N'SLEEP_BPOOL_FLUSH', N'SLEEP_DBSTARTUP',
N'SLEEP_DCOMSTARTUP', N'SLEEP_MASTERDBREADY', N'SLEEP_MASTERMDREADY',
N'SLEEP_MASTERUPGRADED', N'SLEEP_MSDBSTARTUP', N'SLEEP_SYSTEMTASK', N'SLEEP_TASK',
N'SLEEP_TEMPDBSTARTUP', N'SNI_HTTP_ACCEPT', N'SP_SERVER_DIAGNOSTICS_SLEEP',
N'SQLTRACE_BUFFER_FLUSH', N'SQLTRACE_INCREMENTAL_FLUSH_SLEEP',
N'SQLTRACE_WAIT_ENTRIES',
N'WAIT_FOR_RESULTS', N'WAITFOR', N'WAITFOR_TASKSHUTDOWN', N'WAIT_XTP_HOST_WAIT',
N'WAIT_XTP_OFFLINE_CKPT_NEW_LOG', N'WAIT_XTP_CKPT_CLOSE',
N'XE_BUFFERMGR_ALLPROCESSED_EVENT', N'XE_DISPATCHER_JOIN',
N'XE_DISPATCHER_WAIT', N'XE_LIVE_TARGET_TVF', N'XE_TIMER_EVENT',
N'SOS_WORK_DISPATCHER','RESERVED_MEMORY_ALLOCATION_EXT')
AND [waiting_tasks_count] > 0
AND [wait_time_ms] > 100;
`
const sqlAzureDBDatabaseIO = `
SET DEADLOCK_PRIORITY -10;
IF SERVERPROPERTY('EngineEdition') <> 5 BEGIN /*not Azure SQL DB*/
DECLARE @ErrorMessage AS nvarchar(500) = 'Telegraf - Connection string Server:'+ @@SERVERNAME + ',Database:' + DB_NAME() +' is not an Azure SQL DB. Check the database_type parameter in the telegraf configuration.';
RAISERROR (@ErrorMessage,11,1)
RETURN
END
SELECT
'sqlserver_database_io' As [measurement]
,REPLACE(@@SERVERNAME,'\',':') AS [sql_instance]
,DB_NAME() as [database_name]
,vfs.[database_id] /*needed as tempdb is different for each Azure SQL DB as grouping has to be by logical server + db_name + database_id*/
,vfs.[file_id]
,vfs.[io_stall_read_ms] AS [read_latency_ms]
,vfs.[num_of_reads] AS [reads]
,vfs.[num_of_bytes_read] AS [read_bytes]
,vfs.[io_stall_write_ms] AS [write_latency_ms]
,vfs.[num_of_writes] AS [writes]
,vfs.[num_of_bytes_written] AS [write_bytes]
,vfs.[io_stall_queued_read_ms] AS [rg_read_stall_ms]
,vfs.[io_stall_queued_write_ms] AS [rg_write_stall_ms]
,CASE
WHEN (vfs.[database_id] = 0) THEN 'RBPEX'
ELSE b.[logical_filename]
END as [logical_filename]
,CASE
WHEN (vfs.[database_id] = 0) THEN 'RBPEX'
ELSE b.[physical_filename]
END as [physical_filename]
,CASE
WHEN vfs.[file_id] = 2 THEN 'LOG'
ELSE 'DATA'
END AS [file_type]
,ISNULL([size],0)/128 AS [current_size_mb]
,ISNULL(FILEPROPERTY(b.[logical_filename],'SpaceUsed')/128,0) as [space_used_mb]
,DATABASEPROPERTYEX(DB_NAME(), 'Updateability') as replica_updateability
FROM
[sys].[dm_io_virtual_file_stats](NULL,NULL) AS vfs
-- needed to get Tempdb file names on Azure SQL DB so you can join appropriately. Without this had a bug where join was only on file_id
LEFT OUTER join (
SELECT
DB_ID() as [database_id]
,[file_id]
,[logical_filename]= [name] COLLATE SQL_Latin1_General_CP1_CI_AS
,[physical_filename] = [physical_name] COLLATE SQL_Latin1_General_CP1_CI_AS
,[size]
FROM sys.database_files
WHERE
[type] <> 2
UNION ALL
SELECT
2 as [database_id]
,[file_id]
,[logical_filename] = [name]
,[physical_filename] = [physical_name]
,[size]
FROM tempdb.sys.database_files
) b
ON
b.[database_id] = vfs.[database_id]
AND b.[file_id] = vfs.[file_id]
WHERE
vfs.[database_id] IN (DB_ID(),0,2)
`
const sqlAzureDBProperties = `
IF SERVERPROPERTY('EngineEdition') <> 5 BEGIN /*not Azure SQL DB*/
DECLARE @ErrorMessage AS nvarchar(500) = 'Telegraf - Connection string Server:'+ @@SERVERNAME + ',Database:' + DB_NAME() +' is not an Azure SQL DB. Check the database_type parameter in the telegraf configuration.';
RAISERROR (@ErrorMessage,11,1)
RETURN
END
SELECT
'sqlserver_server_properties' AS [measurement]
,REPLACE(@@SERVERNAME,'\',':') AS [sql_instance]
,DB_NAME() as [database_name]
,(SELECT count(*) FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE') AS [cpu_count]
,(SELECT [process_memory_limit_mb] FROM sys.dm_os_job_object) AS [server_memory]
,slo.[edition] as [sku]
,SERVERPROPERTY('EngineEdition') AS [engine_edition]
,slo.[service_objective] AS [hardware_type]
,CASE
WHEN slo.[edition] = 'Hyperscale' then NULL
ELSE CAST(DATABASEPROPERTYEX(DB_NAME(),'MaxSizeInBytes') as bigint)/(1024*1024)
END AS [total_storage_mb]
,(SELECT SUM(CAST(FILEPROPERTY(name, 'SpaceUsed') AS INT) / 128) FROM sys.database_files WHERE type_desc = 'ROWS') AS used_storage_mb
,CASE
WHEN slo.[edition] = 'Hyperscale' then NULL
ELSE (
SELECT (CAST(DATABASEPROPERTYEX(DB_NAME(), 'MaxSizeInBytes') AS BIGINT) / (1024 * 1024) - SUM(CAST(FILEPROPERTY(name, 'SpaceUsed') AS INT) / 128))
FROM sys.database_files
WHERE type_desc = 'ROWS'
)
END AS [available_storage_mb]
,(SELECT SUM(CAST (max_size as BIGINT)) * 8 / (1024 * 1024) FROM sys.database_files WHERE type_desc = 'LOG') AS max_log_mb
,(select DATEDIFF(MINUTE,sqlserver_start_time,GETDATE()) from sys.dm_os_sys_info) as [uptime]
,DATABASEPROPERTYEX(DB_NAME(), 'Updateability') as replica_updateability
FROM sys.[databases] AS d
-- sys.databases.database_id may not match current DB_ID on Azure SQL DB
CROSS JOIN sys.[database_service_objectives] AS slo
WHERE
d.[name] = DB_NAME()
AND slo.[database_id] = DB_ID();
`
const sqlAzureDBOsWaitStats = `
IF SERVERPROPERTY('EngineEdition') <> 5 BEGIN /*not Azure SQL DB*/
DECLARE @ErrorMessage AS nvarchar(500) = 'Telegraf - Connection string Server:'+ @@SERVERNAME + ',Database:' + DB_NAME() +' is not an Azure SQL DB. Check the database_type parameter in the telegraf configuration.';
RAISERROR (@ErrorMessage,11,1)
RETURN
END
SELECT
'sqlserver_waitstats' AS [measurement]
,REPLACE(@@SERVERNAME,'\',':') AS [sql_instance]
,DB_NAME() as [database_name]
,ws.[wait_type]
,[wait_time_ms]
,[wait_time_ms] - [signal_wait_time_ms] AS [resource_wait_ms]
,[signal_wait_time_ms]
,[max_wait_time_ms]
,[waiting_tasks_count]
,CASE
WHEN ws.[wait_type] LIKE 'SOS_SCHEDULER_YIELD' then 'CPU'
WHEN ws.[wait_type] = 'THREADPOOL' THEN 'Worker Thread'
WHEN ws.[wait_type] LIKE 'LCK[_]%' THEN 'Lock'
WHEN ws.[wait_type] LIKE 'LATCH[_]%' THEN 'Latch'
WHEN ws.[wait_type] LIKE 'PAGELATCH[_]%' THEN 'Buffer Latch'
WHEN ws.[wait_type] LIKE 'PAGEIOLATCH[_]%' THEN 'Buffer IO'
WHEN ws.[wait_type] LIKE 'RESOURCE_SEMAPHORE_QUERY_COMPILE%' THEN 'Compilation'
WHEN ws.[wait_type] LIKE 'CLR[_]%' or ws.[wait_type] like 'SQLCLR%' THEN 'SQL CLR'
WHEN ws.[wait_type] LIKE 'DBMIRROR_%' THEN 'Mirroring'
WHEN ws.[wait_type] LIKE 'DTC[_]%' or ws.[wait_type] LIKE 'DTCNEW%' or ws.[wait_type] LIKE 'TRAN_%'
or ws.[wait_type] LIKE 'XACT%' or ws.[wait_type] like 'MSQL_XACT%' THEN 'Transaction'
WHEN ws.[wait_type] LIKE 'SLEEP[_]%'
or ws.[wait_type] IN (
'LAZYWRITER_SLEEP', 'SQLTRACE_BUFFER_FLUSH', 'SQLTRACE_INCREMENTAL_FLUSH_SLEEP',
'SQLTRACE_WAIT_ENTRIES', 'FT_IFTS_SCHEDULER_IDLE_WAIT', 'XE_DISPATCHER_WAIT',
'REQUEST_FOR_DEADLOCK_SEARCH', 'LOGMGR_QUEUE', 'ONDEMAND_TASK_QUEUE',
'CHECKPOINT_QUEUE', 'XE_TIMER_EVENT') THEN 'Idle'
WHEN ws.[wait_type] IN(
'ASYNC_IO_COMPLETION','BACKUPIO','CHKPT','WRITE_COMPLETION',
'IO_QUEUE_LIMIT', 'IO_RETRY') THEN 'Other Disk IO'
WHEN ws.[wait_type] LIKE 'PREEMPTIVE_%' THEN 'Preemptive'
WHEN ws.[wait_type] LIKE 'BROKER[_]%' THEN 'Service Broker'
WHEN ws.[wait_type] IN (
'WRITELOG','LOGBUFFER','LOGMGR_RESERVE_APPEND',
'LOGMGR_FLUSH', 'LOGMGR_PMM_LOG') THEN 'Tran Log IO'
WHEN ws.[wait_type] LIKE 'LOG_RATE%' then 'Log Rate Governor'
WHEN ws.[wait_type] LIKE 'HADR_THROTTLE[_]%'
or ws.[wait_type] = 'THROTTLE_LOG_RATE_LOG_STORAGE' THEN 'HADR Log Rate Governor'
WHEN ws.[wait_type] LIKE 'RBIO_RG%' or ws.[wait_type] like 'WAIT_RBIO_RG%' then 'VLDB Log Rate Governor'
WHEN ws.[wait_type] LIKE 'RBIO[_]%' or ws.[wait_type] like 'WAIT_RBIO[_]%' then 'VLDB RBIO'
WHEN ws.[wait_type] IN(
'ASYNC_NETWORK_IO','EXTERNAL_SCRIPT_NETWORK_IOF',
'NET_WAITFOR_PACKET','PROXY_NETWORK_IO') THEN 'Network IO'
WHEN ws.[wait_type] IN ( 'CXPACKET', 'CXCONSUMER')
or ws.[wait_type] like 'HT%' or ws.[wait_type] like 'BMP%'
or ws.[wait_type] like 'BP%' THEN 'Parallelism'
WHEN ws.[wait_type] IN(
'CMEMTHREAD','CMEMPARTITIONED','EE_PMOLOCK','EXCHANGE',
'RESOURCE_SEMAPHORE','MEMORY_ALLOCATION_EXT',
'RESERVED_MEMORY_ALLOCATION_EXT', 'MEMORY_GRANT_UPDATE') THEN 'Memory'
WHEN ws.[wait_type] IN ('WAITFOR','WAIT_FOR_RESULTS') THEN 'User Wait'
WHEN ws.[wait_type] LIKE 'HADR[_]%' or ws.[wait_type] LIKE 'PWAIT_HADR%'
or ws.[wait_type] LIKE 'REPLICA[_]%' or ws.[wait_type] LIKE 'REPL_%'
or ws.[wait_type] LIKE 'SE_REPL[_]%'
or ws.[wait_type] LIKE 'FCB_REPLICA%' THEN 'Replication'
WHEN ws.[wait_type] LIKE 'SQLTRACE[_]%'
or ws.[wait_type] IN (
'TRACEWRITE', 'SQLTRACE_LOCK', 'SQLTRACE_FILE_BUFFER', 'SQLTRACE_FILE_WRITE_IO_COMPLETION',
'SQLTRACE_FILE_READ_IO_COMPLETION', 'SQLTRACE_PENDING_BUFFER_WRITERS', 'SQLTRACE_SHUTDOWN',
'QUERY_TRACEOUT', 'TRACE_EVTNOTIF') THEN 'Tracing'
WHEN ws.[wait_type] IN (
'FT_RESTART_CRAWL', 'FULLTEXT GATHERER', 'MSSEARCH', 'FT_METADATA_MUTEX',
'FT_IFTSHC_MUTEX', 'FT_IFTSISM_MUTEX', 'FT_IFTS_RWLOCK', 'FT_COMPROWSET_RWLOCK',
'FT_MASTER_MERGE', 'FT_PROPERTYLIST_CACHE', 'FT_MASTER_MERGE_COORDINATOR',
'PWAIT_RESOURCE_SEMAPHORE_FT_PARALLEL_QUERY_SYNC') THEN 'Full Text Search'
ELSE 'Other'
END as [wait_category]
,DATABASEPROPERTYEX(DB_NAME(), 'Updateability') as replica_updateability
FROM sys.dm_os_wait_stats AS ws WITH (NOLOCK)
WHERE
ws.[wait_type] NOT IN (
N'BROKER_EVENTHANDLER', N'BROKER_RECEIVE_WAITFOR', N'BROKER_TASK_STOP',
N'BROKER_TO_FLUSH', N'BROKER_TRANSMITTER', N'CHECKPOINT_QUEUE',
N'CHKPT', N'CLR_AUTO_EVENT', N'CLR_MANUAL_EVENT', N'CLR_SEMAPHORE',
N'DBMIRROR_DBM_EVENT', N'DBMIRROR_EVENTS_QUEUE', N'DBMIRROR_QUEUE',
N'DBMIRRORING_CMD', N'DIRTY_PAGE_POLL', N'DISPATCHER_QUEUE_SEMAPHORE',
N'EXECSYNC', N'FSAGENT', N'FT_IFTS_SCHEDULER_IDLE_WAIT', N'FT_IFTSHC_MUTEX',
N'HADR_CLUSAPI_CALL', N'HADR_FILESTREAM_IOMGR_IOCOMPLETION', N'HADR_LOGCAPTURE_WAIT',
N'HADR_NOTIFICATION_DEQUEUE', N'HADR_TIMER_TASK', N'HADR_WORK_QUEUE',
N'KSOURCE_WAKEUP', N'LAZYWRITER_SLEEP', N'LOGMGR_QUEUE',
N'MEMORY_ALLOCATION_EXT', N'ONDEMAND_TASK_QUEUE',
N'PARALLEL_REDO_WORKER_WAIT_WORK',
N'PREEMPTIVE_HADR_LEASE_MECHANISM', N'PREEMPTIVE_SP_SERVER_DIAGNOSTICS',
N'PREEMPTIVE_OS_LIBRARYOPS', N'PREEMPTIVE_OS_COMOPS', N'PREEMPTIVE_OS_CRYPTOPS',
N'PREEMPTIVE_OS_PIPEOPS','PREEMPTIVE_OS_GENERICOPS', N'PREEMPTIVE_OS_VERIFYTRUST',
N'PREEMPTIVE_OS_DEVICEOPS',
N'PREEMPTIVE_XE_CALLBACKEXECUTE', N'PREEMPTIVE_XE_DISPATCHER',
N'PREEMPTIVE_XE_GETTARGETSTATE', N'PREEMPTIVE_XE_SESSIONCOMMIT',
N'PREEMPTIVE_XE_TARGETINIT', N'PREEMPTIVE_XE_TARGETFINALIZE',
N'PWAIT_ALL_COMPONENTS_INITIALIZED', N'PWAIT_DIRECTLOGCONSUMER_GETNEXT',
N'QDS_PERSIST_TASK_MAIN_LOOP_SLEEP',
N'QDS_ASYNC_QUEUE',
N'QDS_CLEANUP_STALE_QUERIES_TASK_MAIN_LOOP_SLEEP', N'REQUEST_FOR_DEADLOCK_SEARCH',
N'RESOURCE_QUEUE', N'SERVER_IDLE_CHECK', N'SLEEP_BPOOL_FLUSH', N'SLEEP_DBSTARTUP',
N'SLEEP_DCOMSTARTUP', N'SLEEP_MASTERDBREADY', N'SLEEP_MASTERMDREADY',
N'SLEEP_MASTERUPGRADED', N'SLEEP_MSDBSTARTUP', N'SLEEP_SYSTEMTASK', N'SLEEP_TASK',
N'SLEEP_TEMPDBSTARTUP', N'SNI_HTTP_ACCEPT', N'SP_SERVER_DIAGNOSTICS_SLEEP',
N'SQLTRACE_BUFFER_FLUSH', N'SQLTRACE_INCREMENTAL_FLUSH_SLEEP',
N'SQLTRACE_WAIT_ENTRIES',
N'WAIT_FOR_RESULTS', N'WAITFOR', N'WAITFOR_TASKSHUTDOWN', N'WAIT_XTP_HOST_WAIT',
N'WAIT_XTP_OFFLINE_CKPT_NEW_LOG', N'WAIT_XTP_CKPT_CLOSE',
N'XE_BUFFERMGR_ALLPROCESSED_EVENT', N'XE_DISPATCHER_JOIN',
N'XE_DISPATCHER_WAIT', N'XE_LIVE_TARGET_TVF', N'XE_TIMER_EVENT',
N'SOS_WORK_DISPATCHER','RESERVED_MEMORY_ALLOCATION_EXT','SQLTRACE_WAIT_ENTRIES',
N'RBIO_COMM_RETRY')
AND [waiting_tasks_count] > 10
AND [wait_time_ms] > 100;
`
const sqlAzureDBMemoryClerks = `
IF SERVERPROPERTY('EngineEdition') <> 5 BEGIN /*not Azure SQL DB*/
DECLARE @ErrorMessage AS nvarchar(500) = 'Telegraf - Connection string Server:'+ @@SERVERNAME + ',Database:' + DB_NAME() +' is not an Azure SQL DB. Check the database_type parameter in the telegraf configuration.';
RAISERROR (@ErrorMessage,11,1)
RETURN
END
SELECT
'sqlserver_memory_clerks' AS [measurement]
,REPLACE(@@SERVERNAME, '\', ':') AS [sql_instance]
,DB_NAME() AS [database_name]
,mc.[type] AS [clerk_type]
,SUM(mc.[pages_kb]) AS [size_kb]
,DATABASEPROPERTYEX(DB_NAME(), 'Updateability') as replica_updateability
FROM sys.[dm_os_memory_clerks] AS mc WITH (NOLOCK)
GROUP BY
mc.[type]
HAVING
SUM(mc.[pages_kb]) >= 1024
OPTION(RECOMPILE);
`
const sqlAzureDBPerformanceCounters = `
SET DEADLOCK_PRIORITY -10;
IF SERVERPROPERTY('EngineEdition') <> 5 BEGIN /*not Azure SQL DB*/
DECLARE @ErrorMessage AS nvarchar(500) = 'Telegraf - Connection string Server:'+ @@SERVERNAME + ',Database:' + DB_NAME() +' is not an Azure SQL DB. Check the database_type parameter in the telegraf configuration.';
RAISERROR (@ErrorMessage,11,1)
RETURN
END
DECLARE @PCounters TABLE
(
[object_name] nvarchar(128),
[counter_name] nvarchar(128),
[instance_name] nvarchar(128),
[cntr_value] bigint,
[cntr_type] INT ,
Primary Key([object_name],[counter_name],[instance_name])
);
WITH PerfCounters AS (
SELECT DISTINCT
RTrim(spi.[object_name]) [object_name]
,RTrim(spi.[counter_name]) [counter_name]
,CASE WHEN (
RTRIM(spi.[object_name]) LIKE '%:Databases'
OR RTRIM(spi.[object_name]) LIKE '%:Database Replica'
OR RTRIM(spi.[object_name]) LIKE '%:Catalog Metadata'
OR RTRIM(spi.[object_name]) LIKE '%:Query Store'
OR RTRIM(spi.[object_name]) LIKE '%:Columnstore'
OR RTRIM(spi.[object_name]) LIKE '%:Advanced Analytics')
AND TRY_CONVERT([uniqueidentifier], spi.[instance_name]) IS NOT NULL -- for cloud only
THEN ISNULL(d.[name],RTRIM(spi.instance_name)) -- Elastic Pools counters exist for all databases but sys.databases only has current DB value
WHEN
RTRIM([object_name]) LIKE '%:Availability Replica'
AND TRY_CONVERT([uniqueidentifier], spi.[instance_name]) IS NOT NULL -- for cloud only
THEN ISNULL(d.[name],RTRIM(spi.[instance_name])) + RTRIM(SUBSTRING(spi.[instance_name], 37, LEN(spi.[instance_name])))
ELSE RTRIM(spi.instance_name)
END AS [instance_name]
,CAST(spi.[cntr_value] AS BIGINT) AS [cntr_value]
,spi.[cntr_type]
FROM sys.dm_os_performance_counters AS spi
LEFT JOIN sys.databases AS d
ON LEFT(spi.[instance_name], 36) -- some instance_name values have an additional identifier appended after the GUID
= CASE
/*in SQL DB standalone, physical_database_name for master is the GUID of the user database*/
WHEN d.[name] = 'master' AND TRY_CONVERT([uniqueidentifier], d.[physical_database_name]) IS NOT NULL
THEN d.[name]
ELSE d.[physical_database_name]
END
WHERE
/*filter out unnecessary SQL DB system database counters, other than master and tempdb*/
NOT (spi.object_name LIKE 'MSSQL%:Databases%' AND spi.instance_name IN ('model','model_masterdb','model_userdb','msdb','mssqlsystemresource'))
AND
(
counter_name IN (
'SQL Compilations/sec'
,'SQL Re-Compilations/sec'
,'User Connections'
,'Batch Requests/sec'
,'Logouts/sec'
,'Logins/sec'
,'Processes blocked'
,'Latch Waits/sec'
,'Full Scans/sec'
,'Index Searches/sec'
,'Page Splits/sec'
,'Page lookups/sec'
,'Page reads/sec'
,'Page writes/sec'
,'Readahead pages/sec'
,'Lazy writes/sec'
,'Checkpoint pages/sec'
,'Table Lock Escalations/sec'
,'Page life expectancy'
,'Log File(s) Size (KB)'
,'Log File(s) Used Size (KB)'
,'Data File(s) Size (KB)'
,'Transactions/sec'
,'Write Transactions/sec'
,'Active Transactions'
,'Log Growths'
,'Active Temp Tables'
,'Logical Connections'
,'Temp Tables Creation Rate'
,'Temp Tables For Destruction'
,'Free Space in tempdb (KB)'
,'Version Store Size (KB)'
,'Memory Grants Pending'
,'Memory Grants Outstanding'
,'Free list stalls/sec'
,'Buffer cache hit ratio'
,'Buffer cache hit ratio base'
,'Backup/Restore Throughput/sec'
,'Total Server Memory (KB)'
,'Target Server Memory (KB)'
,'Log Flushes/sec'
,'Log Flush Wait Time'
,'Memory broker clerk size'
,'Log Bytes Flushed/sec'
,'Bytes Sent to Replica/sec'
,'Log Send Queue'
,'Bytes Sent to Transport/sec'
,'Sends to Replica/sec'
,'Bytes Sent to Transport/sec'
,'Sends to Transport/sec'
,'Bytes Received from Replica/sec'
,'Receives from Replica/sec'
,'Flow Control Time (ms/sec)'
,'Flow Control/sec'
,'Resent Messages/sec'
,'Redone Bytes/sec'
,'XTP Memory Used (KB)'
,'Transaction Delay'
,'Log Bytes Received/sec'
,'Log Apply Pending Queue'
,'Redone Bytes/sec'
,'Recovery Queue'
,'Log Apply Ready Queue'
,'CPU usage %'
,'CPU usage % base'
,'Queued requests'
,'Requests completed/sec'
,'Blocked tasks'
,'Active memory grant amount (KB)'
,'Disk Read Bytes/sec'
,'Disk Read IO Throttled/sec'
,'Disk Read IO/sec'
,'Disk Write Bytes/sec'
,'Disk Write IO Throttled/sec'
,'Disk Write IO/sec'
,'Used memory (KB)'
,'Forwarded Records/sec'
,'Background Writer pages/sec'
,'Percent Log Used'
,'Log Send Queue KB'
,'Redo Queue KB'
,'Mirrored Write Transactions/sec'
,'Group Commit Time'
,'Group Commits/Sec'
,'Workfiles Created/sec'
,'Worktables Created/sec'
,'Query Store CPU usage'
) OR (
spi.[object_name] LIKE '%User Settable%'
OR spi.[object_name] LIKE '%SQL Errors%'
OR spi.[object_name] LIKE '%Batch Resp Statistics%'
) OR (
spi.[instance_name] IN ('_Total')
AND spi.[counter_name] IN (
'Lock Timeouts/sec'
,'Lock Timeouts (timeout > 0)/sec'
,'Number of Deadlocks/sec'
,'Lock Waits/sec'
,'Latch Waits/sec'
)
)
)
)
INSERT INTO @PCounters select * from PerfCounters
SELECT
'sqlserver_performance' AS [measurement]
,REPLACE(@@SERVERNAME,'\',':') AS [sql_instance]
,DB_NAME() as [database_name]
,pc.[object_name] AS [object]
,pc.[counter_name] AS [counter]
,CASE pc.[instance_name]
WHEN '_Total' THEN 'Total'
ELSE ISNULL(pc.[instance_name],'')
END AS [instance]
,CAST(CASE WHEN pc.[cntr_type] = 537003264 AND pc1.[cntr_value] > 0 THEN (pc.[cntr_value] * 1.0) / (pc1.[cntr_value] * 1.0) * 100 ELSE pc.[cntr_value] END AS float(10)) AS [value]
,cast(pc.[cntr_type] as varchar(25)) as [counter_type]
,DATABASEPROPERTYEX(DB_NAME(), 'Updateability') as replica_updateability
from @PCounters pc
LEFT OUTER JOIN @PCounters AS pc1
ON (
pc.[counter_name] = REPLACE(pc1.[counter_name],' base','')
OR pc.[counter_name] = REPLACE(pc1.[counter_name],' base',' (ms)')
)
AND pc.[object_name] = pc1.[object_name]
AND pc.[instance_name] = pc1.[instance_name]
AND pc1.[counter_name] LIKE '%base'
WHERE
pc.[counter_name] NOT LIKE '% base'
OPTION (RECOMPILE);
`
const sqlAzureDBRequests string = `
IF SERVERPROPERTY('EngineEdition') <> 5 BEGIN /*not Azure SQL DB*/
DECLARE @ErrorMessage AS nvarchar(500) = 'Telegraf - Connection string Server:'+ @@SERVERNAME + ',Database:' + DB_NAME() +' is not an Azure SQL DB. Check the database_type parameter in the telegraf configuration.';
RAISERROR (@ErrorMessage,11,1)
RETURN
END
SELECT
[measurement],[sql_instance],[database_name],[session_id]
,ISNULL([request_id],0) AS [request_id]
,[blocking_session_id],[status],[cpu_time_ms]
,[total_elapsed_time_ms],[logical_reads],[writes]
,[command],[wait_time_ms],[wait_type]
,[wait_resource],[program_name]
,[host_name],[nt_user_name],[login_name]
,[transaction_isolation_level],[granted_query_memory_pages],[percent_complete]
,[statement_text],[objectid],[stmt_object_name]
,[stmt_db_name],[query_hash],[query_plan_hash]
,replica_updateability
,[session_db_name],[open_transaction]
FROM (
SELECT
'sqlserver_requests' AS [measurement]
,REPLACE(@@SERVERNAME,'\',':') AS [sql_instance]
,DB_NAME() as [database_name]
,s.[session_id]
,r.[request_id]
,DB_NAME(COALESCE(r.[database_id], s.[database_id])) AS [session_db_name]
,COALESCE(r.[status], s.[status]) AS [status]
,COALESCE(r.[cpu_time], s.[cpu_time]) AS [cpu_time_ms]
,COALESCE(r.[total_elapsed_time], s.[total_elapsed_time]) AS [total_elapsed_time_ms]
,COALESCE(r.[logical_reads], s.[logical_reads]) AS [logical_reads]
,COALESCE(r.[writes], s.[writes]) AS [writes]
,r.[command]
,r.[wait_time] AS [wait_time_ms]
,r.[wait_type]
,r.[wait_resource]
,NULLIF(r.[blocking_session_id],0) AS [blocking_session_id]
,s.[program_name]
,s.[host_name]
,s.[nt_user_name]
,s.[login_name]
,COALESCE(r.[open_transaction_count], s.[open_transaction_count]) AS [open_transaction]
,LEFT (CASE COALESCE(r.[transaction_isolation_level], s.[transaction_isolation_level])
WHEN 0 THEN '0-Read Committed'
WHEN 1 THEN '1-Read Uncommitted (NOLOCK)'
WHEN 2 THEN '2-Read Committed'
WHEN 3 THEN '3-Repeatable Read'
WHEN 4 THEN '4-Serializable'
WHEN 5 THEN '5-Snapshot'
ELSE CONVERT (varchar(30), r.[transaction_isolation_level]) + '-UNKNOWN'
END, 30) AS [transaction_isolation_level]
,r.[granted_query_memory] AS [granted_query_memory_pages]
,r.[percent_complete]
,SUBSTRING(
qt.[text],
r.[statement_start_offset] / 2 + 1,
(CASE WHEN r.[statement_end_offset] = -1
THEN DATALENGTH(qt.[text])
ELSE r.[statement_end_offset]
END - r.[statement_start_offset]) / 2 + 1
) AS [statement_text]
,qt.[objectid]
,QUOTENAME(OBJECT_SCHEMA_NAME(qt.[objectid], qt.[dbid])) + '.' + QUOTENAME(OBJECT_NAME(qt.[objectid], qt.[dbid])) as [stmt_object_name]
,DB_NAME(qt.[dbid]) AS [stmt_db_name]
,CONVERT(varchar(20),r.[query_hash],1) AS [query_hash]
,CONVERT(varchar(20),r.[query_plan_hash],1) AS [query_plan_hash]
,DATABASEPROPERTYEX(DB_NAME(), 'Updateability') as replica_updateability
,s.[is_user_process]
,[blocking_or_blocked] = COUNT(*) OVER(PARTITION BY ISNULL(NULLIF(r.[blocking_session_id], 0),s.[session_id]))
FROM sys.dm_exec_sessions AS s
LEFT OUTER JOIN sys.dm_exec_requests AS r
ON s.[session_id] = r.[session_id]
OUTER APPLY sys.dm_exec_sql_text(r.[sql_handle]) AS qt
) AS data
WHERE
[blocking_or_blocked] > 1 --Always include blocking or blocked sessions/requests
OR [open_transaction] >= 1 --Always include sessions with open transactions
OR (
[request_id] IS NOT NULL --A request must exists
AND ( --Always fetch user process (in any state), fetch system process only if active
[is_user_process] = 1
OR [status] COLLATE Latin1_General_BIN NOT IN ('background', 'sleeping')
)
AND [session_id] <> @@SPID
)
OPTION(MAXDOP 1);
`
const sqlAzureDBSchedulers string = `
IF SERVERPROPERTY('EngineEdition') <> 5 BEGIN /*not Azure SQL DB*/
DECLARE @ErrorMessage AS nvarchar(500) = 'Telegraf - Connection string Server:'+ @@SERVERNAME + ',Database:' + DB_NAME() +' is not an Azure SQL DB. Check the database_type parameter in the telegraf configuration.';
RAISERROR (@ErrorMessage,11,1)
RETURN
END
SELECT
'sqlserver_schedulers' AS [measurement]
,REPLACE(@@SERVERNAME, '\', ':') AS [sql_instance]
,CAST(s.[scheduler_id] AS VARCHAR(4)) AS [scheduler_id]
,CAST(s.[cpu_id] AS VARCHAR(4)) AS [cpu_id]
,s.[is_online]
,s.[is_idle]
,s.[preemptive_switches_count]
,s.[context_switches_count]
,s.[current_tasks_count]
,s.[runnable_tasks_count]
,s.[current_workers_count]
,s.[active_workers_count]
,s.[work_queue_count]
,s.[pending_disk_io_count]
,s.[load_factor]
,s.[yield_count]
,s.[total_cpu_usage_ms]
,s.[total_scheduler_delay_ms]
,DATABASEPROPERTYEX(DB_NAME(), 'Updateability') as replica_updateability
FROM sys.dm_os_schedulers AS s
`

View file

@ -0,0 +1,462 @@
package sqlserver
import (
"os"
"testing"
"github.com/stretchr/testify/require"
"github.com/influxdata/telegraf/config"
"github.com/influxdata/telegraf/testutil"
)
func TestAzureSQLIntegration_Database_ResourceStats_Query(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
if os.Getenv("AZURESQL_DB_CONNECTION_STRING") == "" {
t.Skip("Missing environment variable AZURESQL_DB_CONNECTION_STRING")
}
connectionString := os.Getenv("AZURESQL_DB_CONNECTION_STRING")
sl := config.NewSecret([]byte(connectionString))
server := &SQLServer{
Servers: []*config.Secret{&sl},
IncludeQuery: []string{"AzureSQLDBResourceStats"},
AuthMethod: "connection_string",
DatabaseType: "AzureSQLDB",
}
var acc testutil.Accumulator
require.NoError(t, server.Start(&acc))
require.NoError(t, server.Gather(&acc))
require.True(t, acc.HasMeasurement("sqlserver_azure_db_resource_stats"))
require.True(t, acc.HasTag("sqlserver_azure_db_resource_stats", "sql_instance"))
require.True(t, acc.HasTag("sqlserver_azure_db_resource_stats", "database_name"))
require.True(t, acc.HasFloatField("sqlserver_azure_db_resource_stats", "avg_cpu_percent"))
require.True(t, acc.HasFloatField("sqlserver_azure_db_resource_stats", "avg_data_io_percent"))
require.True(t, acc.HasFloatField("sqlserver_azure_db_resource_stats", "avg_log_write_percent"))
require.True(t, acc.HasFloatField("sqlserver_azure_db_resource_stats", "avg_memory_usage_percent"))
require.True(t, acc.HasFloatField("sqlserver_azure_db_resource_stats", "xtp_storage_percent"))
require.True(t, acc.HasFloatField("sqlserver_azure_db_resource_stats", "max_worker_percent"))
require.True(t, acc.HasFloatField("sqlserver_azure_db_resource_stats", "max_session_percent"))
require.True(t, acc.HasField("sqlserver_azure_db_resource_stats", "dtu_limit")) // Can be null.
require.True(t, acc.HasField("sqlserver_azure_db_resource_stats", "avg_login_rate_percent")) // Can be null.
require.True(t, acc.HasField("sqlserver_azure_db_resource_stats", "end_time")) // Time field.
require.True(t, acc.HasFloatField("sqlserver_azure_db_resource_stats", "avg_instance_memory_percent"))
require.True(t, acc.HasFloatField("sqlserver_azure_db_resource_stats", "avg_instance_cpu_percent"))
require.True(t, acc.HasTag("sqlserver_azure_db_resource_stats", "replica_updateability"))
// This query should only return one row
require.Len(t, acc.Metrics, 1)
server.Stop()
}
func TestAzureSQLIntegration_Database_ResourceGovernance_Query(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
if os.Getenv("AZURESQL_DB_CONNECTION_STRING") == "" {
t.Skip("Missing environment variable AZURESQL_DB_CONNECTION_STRING")
}
connectionString := os.Getenv("AZURESQL_DB_CONNECTION_STRING")
sl := config.NewSecret([]byte(connectionString))
server := &SQLServer{
Servers: []*config.Secret{&sl},
IncludeQuery: []string{"AzureSQLDBResourceGovernance"},
AuthMethod: "connection_string",
DatabaseType: "AzureSQLDB",
}
var acc testutil.Accumulator
require.NoError(t, server.Start(&acc))
require.NoError(t, server.Gather(&acc))
require.True(t, acc.HasMeasurement("sqlserver_db_resource_governance"))
require.True(t, acc.HasTag("sqlserver_db_resource_governance", "sql_instance"))
require.True(t, acc.HasTag("sqlserver_db_resource_governance", "database_name"))
require.True(t, acc.HasTag("sqlserver_db_resource_governance", "slo_name"))
require.True(t, acc.HasInt64Field("sqlserver_db_resource_governance", "dtu_limit"))
require.True(t, acc.HasInt64Field("sqlserver_db_resource_governance", "max_cpu"))
require.True(t, acc.HasInt64Field("sqlserver_db_resource_governance", "cap_cpu"))
require.True(t, acc.HasInt64Field("sqlserver_db_resource_governance", "instance_cap_cpu"))
require.True(t, acc.HasInt64Field("sqlserver_db_resource_governance", "max_db_memory"))
require.True(t, acc.HasInt64Field("sqlserver_db_resource_governance", "max_db_max_size_in_mb"))
require.True(t, acc.HasInt64Field("sqlserver_db_resource_governance", "db_file_growth_in_mb"))
require.True(t, acc.HasInt64Field("sqlserver_db_resource_governance", "log_size_in_mb"))
require.True(t, acc.HasInt64Field("sqlserver_db_resource_governance", "instance_max_worker_threads"))
require.True(t, acc.HasInt64Field("sqlserver_db_resource_governance", "primary_group_max_workers"))
require.True(t, acc.HasInt64Field("sqlserver_db_resource_governance", "instance_max_log_rate"))
require.True(t, acc.HasInt64Field("sqlserver_db_resource_governance", "primary_min_log_rate"))
require.True(t, acc.HasInt64Field("sqlserver_db_resource_governance", "primary_max_log_rate"))
require.True(t, acc.HasInt64Field("sqlserver_db_resource_governance", "primary_group_min_io"))
require.True(t, acc.HasInt64Field("sqlserver_db_resource_governance", "primary_group_max_io"))
require.True(t, acc.HasFloatField("sqlserver_db_resource_governance", "primary_group_min_cpu"))
require.True(t, acc.HasFloatField("sqlserver_db_resource_governance", "primary_group_max_cpu"))
require.True(t, acc.HasInt64Field("sqlserver_db_resource_governance", "primary_pool_max_workers"))
require.True(t, acc.HasInt64Field("sqlserver_db_resource_governance", "pool_max_io"))
require.True(t, acc.HasInt64Field("sqlserver_db_resource_governance", "checkpoint_rate_mbps"))
require.True(t, acc.HasInt64Field("sqlserver_db_resource_governance", "checkpoint_rate_io"))
require.True(t, acc.HasInt64Field("sqlserver_db_resource_governance", "volume_local_iops"))
require.True(t, acc.HasInt64Field("sqlserver_db_resource_governance", "volume_managed_xstore_iops"))
require.True(t, acc.HasInt64Field("sqlserver_db_resource_governance", "volume_external_xstore_iops"))
require.True(t, acc.HasInt64Field("sqlserver_db_resource_governance", "volume_type_local_iops"))
require.True(t, acc.HasInt64Field("sqlserver_db_resource_governance", "volume_type_managed_xstore_iops"))
require.True(t, acc.HasInt64Field("sqlserver_db_resource_governance", "volume_type_external_xstore_iops"))
require.True(t, acc.HasInt64Field("sqlserver_db_resource_governance", "volume_pfs_iops"))
require.True(t, acc.HasInt64Field("sqlserver_db_resource_governance", "volume_type_pfs_iops"))
require.True(t, acc.HasTag("sqlserver_db_resource_governance", "replica_updateability"))
server.Stop()
}
func TestAzureSQLIntegration_Database_WaitStats_Query(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
if os.Getenv("AZURESQL_DB_CONNECTION_STRING") == "" {
t.Skip("Missing environment variable AZURESQL_DB_CONNECTION_STRING")
}
connectionString := os.Getenv("AZURESQL_DB_CONNECTION_STRING")
sl := config.NewSecret([]byte(connectionString))
server := &SQLServer{
Servers: []*config.Secret{&sl},
IncludeQuery: []string{"AzureSQLDBWaitStats"},
AuthMethod: "connection_string",
DatabaseType: "AzureSQLDB",
}
var acc testutil.Accumulator
require.NoError(t, server.Start(&acc))
require.NoError(t, server.Gather(&acc))
require.True(t, acc.HasMeasurement("sqlserver_azuredb_waitstats"))
require.True(t, acc.HasTag("sqlserver_azuredb_waitstats", "sql_instance"))
require.True(t, acc.HasTag("sqlserver_azuredb_waitstats", "database_name"))
require.True(t, acc.HasTag("sqlserver_azuredb_waitstats", "wait_type"))
require.True(t, acc.HasInt64Field("sqlserver_azuredb_waitstats", "wait_time_ms"))
require.True(t, acc.HasInt64Field("sqlserver_azuredb_waitstats", "resource_wait_ms"))
require.True(t, acc.HasInt64Field("sqlserver_azuredb_waitstats", "signal_wait_time_ms"))
require.True(t, acc.HasInt64Field("sqlserver_azuredb_waitstats", "max_wait_time_ms"))
require.True(t, acc.HasInt64Field("sqlserver_azuredb_waitstats", "waiting_tasks_count"))
require.True(t, acc.HasTag("sqlserver_azuredb_waitstats", "replica_updateability"))
server.Stop()
}
func TestAzureSQLIntegration_Database_DatabaseIO_Query(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
if os.Getenv("AZURESQL_DB_CONNECTION_STRING") == "" {
t.Skip("Missing environment variable AZURESQL_DB_CONNECTION_STRING")
}
connectionString := os.Getenv("AZURESQL_DB_CONNECTION_STRING")
sl := config.NewSecret([]byte(connectionString))
server := &SQLServer{
Servers: []*config.Secret{&sl},
IncludeQuery: []string{"AzureSQLDBDatabaseIO"},
AuthMethod: "connection_string",
DatabaseType: "AzureSQLDB",
}
var acc testutil.Accumulator
require.NoError(t, server.Start(&acc))
require.NoError(t, server.Gather(&acc))
require.True(t, acc.HasMeasurement("sqlserver_database_io"))
require.True(t, acc.HasTag("sqlserver_database_io", "sql_instance"))
require.True(t, acc.HasTag("sqlserver_database_io", "database_name"))
require.True(t, acc.HasInt64Field("sqlserver_database_io", "database_id"))
require.True(t, acc.HasInt64Field("sqlserver_database_io", "file_id"))
require.True(t, acc.HasInt64Field("sqlserver_database_io", "read_latency_ms"))
require.True(t, acc.HasInt64Field("sqlserver_database_io", "reads"))
require.True(t, acc.HasInt64Field("sqlserver_database_io", "read_bytes"))
require.True(t, acc.HasInt64Field("sqlserver_database_io", "write_latency_ms"))
require.True(t, acc.HasInt64Field("sqlserver_database_io", "writes"))
require.True(t, acc.HasInt64Field("sqlserver_database_io", "write_bytes"))
require.True(t, acc.HasInt64Field("sqlserver_database_io", "rg_read_stall_ms"))
require.True(t, acc.HasInt64Field("sqlserver_database_io", "rg_write_stall_ms"))
require.True(t, acc.HasTag("sqlserver_database_io", "logical_filename"))
require.True(t, acc.HasTag("sqlserver_database_io", "physical_filename"))
require.True(t, acc.HasTag("sqlserver_database_io", "file_type"))
require.True(t, acc.HasInt64Field("sqlserver_database_io", "current_size_mb"))
require.True(t, acc.HasInt64Field("sqlserver_database_io", "space_used_mb"))
require.True(t, acc.HasTag("sqlserver_database_io", "replica_updateability"))
server.Stop()
}
func TestAzureSQLIntegration_Database_ServerProperties_Query(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
if os.Getenv("AZURESQL_DB_CONNECTION_STRING") == "" {
t.Skip("Missing environment variable AZURESQL_DB_CONNECTION_STRING")
}
connectionString := os.Getenv("AZURESQL_DB_CONNECTION_STRING")
sl := config.NewSecret([]byte(connectionString))
server := &SQLServer{
Servers: []*config.Secret{&sl},
IncludeQuery: []string{"AzureSQLDBServerProperties"},
AuthMethod: "connection_string",
DatabaseType: "AzureSQLDB",
}
var acc testutil.Accumulator
require.NoError(t, server.Start(&acc))
require.NoError(t, server.Gather(&acc))
require.True(t, acc.HasMeasurement("sqlserver_server_properties"))
require.True(t, acc.HasTag("sqlserver_server_properties", "sql_instance"))
require.True(t, acc.HasTag("sqlserver_server_properties", "database_name"))
require.True(t, acc.HasInt64Field("sqlserver_server_properties", "cpu_count"))
require.True(t, acc.HasInt64Field("sqlserver_server_properties", "server_memory"))
require.True(t, acc.HasTag("sqlserver_server_properties", "sku"))
require.True(t, acc.HasInt64Field("sqlserver_server_properties", "engine_edition"))
require.True(t, acc.HasTag("sqlserver_server_properties", "hardware_type"))
require.True(t, acc.HasInt64Field("sqlserver_server_properties", "total_storage_mb"))
require.True(t, acc.HasInt64Field("sqlserver_server_properties", "available_storage_mb"))
require.True(t, acc.HasField("sqlserver_server_properties", "uptime")) // Time field.
require.True(t, acc.HasTag("sqlserver_server_properties", "replica_updateability"))
// This query should only return one row
require.Len(t, acc.Metrics, 1)
server.Stop()
}
func TestAzureSQLIntegration_Database_OsWaitstats_Query(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
if os.Getenv("AZURESQL_DB_CONNECTION_STRING") == "" {
t.Skip("Missing environment variable AZURESQL_DB_CONNECTION_STRING")
}
connectionString := os.Getenv("AZURESQL_DB_CONNECTION_STRING")
sl := config.NewSecret([]byte(connectionString))
server := &SQLServer{
Servers: []*config.Secret{&sl},
IncludeQuery: []string{"AzureSQLDBOsWaitstats"},
AuthMethod: "connection_string",
DatabaseType: "AzureSQLDB",
}
var acc testutil.Accumulator
require.NoError(t, server.Start(&acc))
require.NoError(t, server.Gather(&acc))
require.True(t, acc.HasMeasurement("sqlserver_waitstats"))
require.True(t, acc.HasTag("sqlserver_waitstats", "sql_instance"))
require.True(t, acc.HasTag("sqlserver_waitstats", "database_name"))
require.True(t, acc.HasTag("sqlserver_waitstats", "wait_type"))
require.True(t, acc.HasInt64Field("sqlserver_waitstats", "wait_time_ms"))
require.True(t, acc.HasInt64Field("sqlserver_waitstats", "resource_wait_ms"))
require.True(t, acc.HasInt64Field("sqlserver_waitstats", "signal_wait_time_ms"))
require.True(t, acc.HasInt64Field("sqlserver_waitstats", "max_wait_time_ms"))
require.True(t, acc.HasInt64Field("sqlserver_waitstats", "waiting_tasks_count"))
require.True(t, acc.HasTag("sqlserver_waitstats", "wait_category"))
require.True(t, acc.HasTag("sqlserver_waitstats", "replica_updateability"))
server.Stop()
}
func TestAzureSQLIntegration_Database_MemoryClerks_Query(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
if os.Getenv("AZURESQL_DB_CONNECTION_STRING") == "" {
t.Skip("Missing environment variable AZURESQL_DB_CONNECTION_STRING")
}
connectionString := os.Getenv("AZURESQL_DB_CONNECTION_STRING")
sl := config.NewSecret([]byte(connectionString))
server := &SQLServer{
Servers: []*config.Secret{&sl},
IncludeQuery: []string{"AzureSQLDBMemoryClerks"},
AuthMethod: "connection_string",
DatabaseType: "AzureSQLDB",
}
var acc testutil.Accumulator
require.NoError(t, server.Start(&acc))
require.NoError(t, server.Gather(&acc))
require.True(t, acc.HasMeasurement("sqlserver_memory_clerks"))
require.True(t, acc.HasTag("sqlserver_memory_clerks", "sql_instance"))
require.True(t, acc.HasTag("sqlserver_memory_clerks", "database_name"))
require.True(t, acc.HasTag("sqlserver_memory_clerks", "clerk_type"))
require.True(t, acc.HasInt64Field("sqlserver_memory_clerks", "size_kb"))
require.True(t, acc.HasTag("sqlserver_memory_clerks", "replica_updateability"))
server.Stop()
}
func TestAzureSQLIntegration_Database_PerformanceCounters_Query(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
if os.Getenv("AZURESQL_DB_CONNECTION_STRING") == "" {
t.Skip("Missing environment variable AZURESQL_DB_CONNECTION_STRING")
}
connectionString := os.Getenv("AZURESQL_DB_CONNECTION_STRING")
sl := config.NewSecret([]byte(connectionString))
server := &SQLServer{
Servers: []*config.Secret{&sl},
IncludeQuery: []string{"AzureSQLDBPerformanceCounters"},
AuthMethod: "connection_string",
DatabaseType: "AzureSQLDB",
}
var acc testutil.Accumulator
require.NoError(t, server.Start(&acc))
require.NoError(t, server.Gather(&acc))
require.True(t, acc.HasMeasurement("sqlserver_performance"))
require.True(t, acc.HasTag("sqlserver_performance", "sql_instance"))
require.True(t, acc.HasTag("sqlserver_performance", "database_name"))
require.True(t, acc.HasTag("sqlserver_performance", "object"))
require.True(t, acc.HasTag("sqlserver_performance", "counter"))
require.True(t, acc.HasTag("sqlserver_performance", "instance"))
require.True(t, acc.HasFloatField("sqlserver_performance", "value"))
require.True(t, acc.HasTag("sqlserver_performance", "counter_type"))
require.True(t, acc.HasTag("sqlserver_performance", "replica_updateability"))
server.Stop()
}
func TestAzureSQLIntegration_Database_Requests_Query(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
if os.Getenv("AZURESQL_DB_CONNECTION_STRING") == "" {
t.Skip("Missing environment variable AZURESQL_DB_CONNECTION_STRING")
}
connectionString := os.Getenv("AZURESQL_DB_CONNECTION_STRING")
sl := config.NewSecret([]byte(connectionString))
server := &SQLServer{
Servers: []*config.Secret{&sl},
IncludeQuery: []string{"AzureSQLDBRequests"},
AuthMethod: "connection_string",
DatabaseType: "AzureSQLDB",
}
var acc testutil.Accumulator
require.NoError(t, server.Start(&acc))
require.NoError(t, server.Gather(&acc))
require.True(t, acc.HasMeasurement("sqlserver_requests"))
require.True(t, acc.HasTag("sqlserver_requests", "sql_instance"))
require.True(t, acc.HasTag("sqlserver_requests", "database_name"))
require.True(t, acc.HasInt64Field("sqlserver_requests", "session_id"))
require.True(t, acc.HasInt64Field("sqlserver_requests", "request_id"))
require.True(t, acc.HasTag("sqlserver_requests", "session_db_name"))
require.True(t, acc.HasTag("sqlserver_requests", "status"))
require.True(t, acc.HasInt64Field("sqlserver_requests", "cpu_time_ms"))
require.True(t, acc.HasInt64Field("sqlserver_requests", "total_elapsed_time_ms"))
require.True(t, acc.HasInt64Field("sqlserver_requests", "logical_reads"))
require.True(t, acc.HasInt64Field("sqlserver_requests", "writes"))
require.True(t, acc.HasTag("sqlserver_requests", "command"))
require.True(t, acc.HasInt64Field("sqlserver_requests", "wait_time_ms"))
require.True(t, acc.HasField("sqlserver_requests", "wait_type")) // Can be null.
require.True(t, acc.HasTag("sqlserver_requests", "wait_resource"))
require.True(t, acc.HasInt64Field("sqlserver_requests", "blocking_session_id"))
require.True(t, acc.HasTag("sqlserver_requests", "program_name"))
require.True(t, acc.HasTag("sqlserver_requests", "host_name"))
require.True(t, acc.HasField("sqlserver_requests", "nt_user_name")) // Can be null.
require.True(t, acc.HasTag("sqlserver_requests", "login_name"))
require.True(t, acc.HasInt64Field("sqlserver_requests", "open_transaction"))
require.True(t, acc.HasTag("sqlserver_requests", "transaction_isolation_level"))
require.True(t, acc.HasInt64Field("sqlserver_requests", "granted_query_memory_pages"))
require.True(t, acc.HasFloatField("sqlserver_requests", "percent_complete"))
require.True(t, acc.HasTag("sqlserver_requests", "statement_text"))
require.True(t, acc.HasField("sqlserver_requests", "objectid")) // Can be null.
require.True(t, acc.HasField("sqlserver_requests", "stmt_object_name")) // Can be null.
require.True(t, acc.HasField("sqlserver_requests", "stmt_db_name")) // Can be null.
require.True(t, acc.HasTag("sqlserver_requests", "query_hash"))
require.True(t, acc.HasTag("sqlserver_requests", "query_plan_hash"))
require.True(t, acc.HasTag("sqlserver_requests", "replica_updateability"))
server.Stop()
}
func TestAzureSQLIntegration_Database_Schedulers_Query(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
if os.Getenv("AZURESQL_DB_CONNECTION_STRING") == "" {
t.Skip("Missing environment variable AZURESQL_DB_CONNECTION_STRING")
}
connectionString := os.Getenv("AZURESQL_DB_CONNECTION_STRING")
sl := config.NewSecret([]byte(connectionString))
server := &SQLServer{
Servers: []*config.Secret{&sl},
IncludeQuery: []string{"AzureSQLDBSchedulers"},
AuthMethod: "connection_string",
DatabaseType: "AzureSQLDB",
}
var acc testutil.Accumulator
require.NoError(t, server.Start(&acc))
require.NoError(t, server.Gather(&acc))
require.True(t, acc.HasMeasurement("sqlserver_schedulers"))
require.True(t, acc.HasTag("sqlserver_schedulers", "sql_instance"))
require.True(t, acc.HasTag("sqlserver_schedulers", "scheduler_id"))
require.True(t, acc.HasTag("sqlserver_schedulers", "cpu_id"))
require.True(t, acc.HasField("sqlserver_schedulers", "is_online")) // Bool field.
require.True(t, acc.HasField("sqlserver_schedulers", "is_idle")) // Bool field.
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "preemptive_switches_count"))
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "context_switches_count"))
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "current_tasks_count"))
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "runnable_tasks_count"))
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "current_workers_count"))
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "active_workers_count"))
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "work_queue_count"))
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "pending_disk_io_count"))
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "load_factor"))
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "yield_count"))
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "total_cpu_usage_ms"))
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "total_scheduler_delay_ms"))
require.True(t, acc.HasTag("sqlserver_schedulers", "replica_updateability"))
server.Stop()
}

View file

@ -0,0 +1,572 @@
//nolint:lll // conditionally long lines allowed
package sqlserver
import (
_ "github.com/microsoft/go-mssqldb" // go-mssqldb initialization
)
// ------------------------------------------------------------------------------------------------
// ------------------ Azure Managed Instance ------------------------------------------------------
// ------------------------------------------------------------------------------------------------
const sqlAzureMIProperties = `
IF SERVERPROPERTY('EngineEdition') <> 8 BEGIN /*not Azure Managed Instance*/
DECLARE @ErrorMessage AS nvarchar(500) = 'Telegraf - Connection string Server:'+ @@SERVERNAME + ',Database:' + DB_NAME() +' is not an Azure Managed Instance. Check the database_type parameter in the telegraf configuration.';
RAISERROR (@ErrorMessage,11,1)
RETURN
END
SELECT TOP 1
'sqlserver_server_properties' AS [measurement]
,REPLACE(@@SERVERNAME,'\',':') AS [sql_instance]
,[virtual_core_count] AS [cpu_count]
,(SELECT [process_memory_limit_mb] FROM sys.dm_os_job_object) AS [server_memory]
,[sku]
,SERVERPROPERTY('EngineEdition') AS [engine_edition]
,[hardware_generation] AS [hardware_type]
,cast([reserved_storage_mb] as bigint) AS [total_storage_mb]
,cast(([reserved_storage_mb] - [storage_space_used_mb]) as bigint) AS [available_storage_mb]
,(SELECT DATEDIFF(MINUTE,[sqlserver_start_time],GETDATE()) from sys.dm_os_sys_info) as [uptime]
,SERVERPROPERTY('ProductVersion') AS [sql_version]
,LEFT(@@VERSION,CHARINDEX(' - ',@@VERSION)) AS [sql_version_desc]
,[db_online]
,[db_restoring]
,[db_recovering]
,[db_recoveryPending]
,[db_suspect]
,DATABASEPROPERTYEX(DB_NAME(), 'Updateability') as replica_updateability
FROM sys.server_resource_stats
CROSS APPLY (
SELECT
SUM( CASE WHEN [state] = 0 THEN 1 ELSE 0 END ) AS [db_online]
,SUM( CASE WHEN [state] = 1 THEN 1 ELSE 0 END ) AS [db_restoring]
,SUM( CASE WHEN [state] = 2 THEN 1 ELSE 0 END ) AS [db_recovering]
,SUM( CASE WHEN [state] = 3 THEN 1 ELSE 0 END ) AS [db_recoveryPending]
,SUM( CASE WHEN [state] = 4 THEN 1 ELSE 0 END ) AS [db_suspect]
,SUM( CASE WHEN [state] IN (6,10) THEN 1 ELSE 0 END ) AS [db_offline]
FROM sys.databases
) AS dbs
ORDER BY
[start_time] DESC;
`
const sqlAzureMIResourceStats = `
IF SERVERPROPERTY('EngineEdition') <> 8 BEGIN /*not Azure Managed Instance*/
DECLARE @ErrorMessage AS nvarchar(500) = 'Telegraf - Connection string Server:'+ @@SERVERNAME + ',Database:' + DB_NAME() +' is not an Azure Managed Instance. Check the database_type parameter in the telegraf configuration.';
RAISERROR (@ErrorMessage,11,1)
RETURN
END
SELECT TOP(1)
'sqlserver_azure_db_resource_stats' AS [measurement]
,REPLACE(@@SERVERNAME,'\',':') AS [sql_instance]
,cast([avg_cpu_percent] as float) as [avg_cpu_percent]
,DATABASEPROPERTYEX(DB_NAME(), 'Updateability') as replica_updateability
FROM
sys.server_resource_stats
ORDER BY
[end_time] DESC;
`
const sqlAzureMIResourceGovernance string = `
IF SERVERPROPERTY('EngineEdition') <> 8 BEGIN /*not Azure Managed Instance*/
DECLARE @ErrorMessage AS nvarchar(500) = 'Telegraf - Connection string Server:'+ @@SERVERNAME + ',Database:' + DB_NAME() +' is not an Azure Managed Instance. Check the database_type parameter in the telegraf configuration.';
RAISERROR (@ErrorMessage,11,1)
RETURN
END
SELECT
'sqlserver_instance_resource_governance' AS [measurement]
,REPLACE(@@SERVERNAME,'\',':') AS [sql_instance]
,[instance_cap_cpu]
,[instance_max_log_rate]
,[instance_max_worker_threads]
,[tempdb_log_file_number]
,[volume_local_iops]
,[volume_external_xstore_iops]
,[volume_managed_xstore_iops]
,[volume_type_local_iops] as [voltype_local_iops]
,[volume_type_managed_xstore_iops] as [voltype_man_xtore_iops]
,[volume_type_external_xstore_iops] as [voltype_ext_xtore_iops]
,[volume_external_xstore_iops] as [vol_ext_xtore_iops]
,DATABASEPROPERTYEX(DB_NAME(), 'Updateability') as replica_updateability
FROM sys.dm_instance_resource_governance;
`
const sqlAzureMIDatabaseIO = `
SET DEADLOCK_PRIORITY -10;
IF SERVERPROPERTY('EngineEdition') <> 8 BEGIN /*not Azure Managed Instance*/
DECLARE @ErrorMessage AS nvarchar(500) = 'Telegraf - Connection string Server:'+ @@SERVERNAME + ',Database:' + DB_NAME() +' is not an Azure Managed Instance. Check the database_type parameter in the telegraf configuration.';
RAISERROR (@ErrorMessage,11,1)
RETURN
END
SELECT
'sqlserver_database_io' AS [measurement]
,REPLACE(@@SERVERNAME,'\',':') AS [sql_instance]
,DB_NAME(mf.[database_id]) AS [database_name]
,COALESCE(mf.[physical_name],'RBPEX') AS [physical_filename] --RPBEX = Resilient Buffer Pool Extension
,COALESCE(mf.[name],'RBPEX') AS [logical_filename] --RPBEX = Resilient Buffer Pool Extension
,mf.[type_desc] AS [file_type]
,vfs.[io_stall_read_ms] AS [read_latency_ms]
,vfs.[num_of_reads] AS [reads]
,vfs.[num_of_bytes_read] AS [read_bytes]
,vfs.[io_stall_write_ms] AS [write_latency_ms]
,vfs.[num_of_writes] AS [writes]
,vfs.[num_of_bytes_written] AS [write_bytes]
,vfs.io_stall_queued_read_ms AS [rg_read_stall_ms]
,vfs.io_stall_queued_write_ms AS [rg_write_stall_ms]
,DATABASEPROPERTYEX(DB_NAME(), 'Updateability') as replica_updateability
FROM sys.dm_io_virtual_file_stats(NULL, NULL) AS vfs
LEFT OUTER JOIN sys.master_files AS mf WITH (NOLOCK)
ON vfs.[database_id] = mf.[database_id]
AND vfs.[file_id] = mf.[file_id]
WHERE
vfs.[database_id] < 32760
`
const sqlAzureMIMemoryClerks = `
IF SERVERPROPERTY('EngineEdition') <> 8 BEGIN /*not Azure Managed Instance*/
DECLARE @ErrorMessage AS nvarchar(500) = 'Telegraf - Connection string Server:'+ @@SERVERNAME + ',Database:' + DB_NAME() +' is not an Azure Managed Instance. Check the database_type parameter in the telegraf configuration.';
RAISERROR (@ErrorMessage,11,1)
RETURN
END
SELECT
'sqlserver_memory_clerks' AS [measurement]
,REPLACE(@@SERVERNAME, '\', ':') AS [sql_instance]
,mc.[type] AS [clerk_type]
,SUM(mc.[pages_kb]) AS [size_kb]
,DATABASEPROPERTYEX(DB_NAME(), 'Updateability') as replica_updateability
FROM sys.[dm_os_memory_clerks] AS mc WITH (NOLOCK)
GROUP BY
mc.[type]
HAVING
SUM(mc.[pages_kb]) >= 1024
OPTION(RECOMPILE);
`
const sqlAzureMIOsWaitStats = `
IF SERVERPROPERTY('EngineEdition') <> 8 BEGIN /*not Azure Managed Instance*/
DECLARE @ErrorMessage AS nvarchar(500) = 'Telegraf - Connection string Server:'+ @@SERVERNAME + ',Database:' + DB_NAME() +' is not an Azure Managed Instance. Check the database_type parameter in the telegraf configuration.';
RAISERROR (@ErrorMessage,11,1)
RETURN
END
SELECT
'sqlserver_waitstats' AS [measurement]
,REPLACE(@@SERVERNAME,'\',':') AS [sql_instance]
,ws.[wait_type]
,[wait_time_ms]
,[wait_time_ms] - [signal_wait_time_ms] AS [resource_wait_ms]
,[signal_wait_time_ms]
,[max_wait_time_ms]
,[waiting_tasks_count]
,CASE
WHEN ws.[wait_type] LIKE 'SOS_SCHEDULER_YIELD' then 'CPU'
WHEN ws.[wait_type] = 'THREADPOOL' THEN 'Worker Thread'
WHEN ws.[wait_type] LIKE 'LCK[_]%' THEN 'Lock'
WHEN ws.[wait_type] LIKE 'LATCH[_]%' THEN 'Latch'
WHEN ws.[wait_type] LIKE 'PAGELATCH[_]%' THEN 'Buffer Latch'
WHEN ws.[wait_type] LIKE 'PAGEIOLATCH[_]%' THEN 'Buffer IO'
WHEN ws.[wait_type] LIKE 'RESOURCE_SEMAPHORE_QUERY_COMPILE%' THEN 'Compilation'
WHEN ws.[wait_type] LIKE 'CLR[_]%' or ws.[wait_type] like 'SQLCLR%' THEN 'SQL CLR'
WHEN ws.[wait_type] LIKE 'DBMIRROR_%' THEN 'Mirroring'
WHEN ws.[wait_type] LIKE 'DTC[_]%' or ws.[wait_type] LIKE 'DTCNEW%' or ws.[wait_type] LIKE 'TRAN_%'
or ws.[wait_type] LIKE 'XACT%' or ws.[wait_type] like 'MSQL_XACT%' THEN 'Transaction'
WHEN ws.[wait_type] LIKE 'SLEEP[_]%'
or ws.[wait_type] IN (
'LAZYWRITER_SLEEP', 'SQLTRACE_BUFFER_FLUSH', 'SQLTRACE_INCREMENTAL_FLUSH_SLEEP',
'SQLTRACE_WAIT_ENTRIES', 'FT_IFTS_SCHEDULER_IDLE_WAIT', 'XE_DISPATCHER_WAIT',
'REQUEST_FOR_DEADLOCK_SEARCH', 'LOGMGR_QUEUE', 'ONDEMAND_TASK_QUEUE',
'CHECKPOINT_QUEUE', 'XE_TIMER_EVENT') THEN 'Idle'
WHEN ws.[wait_type] IN(
'ASYNC_IO_COMPLETION','BACKUPIO','CHKPT','WRITE_COMPLETION',
'IO_QUEUE_LIMIT', 'IO_RETRY') THEN 'Other Disk IO'
WHEN ws.[wait_type] LIKE 'PREEMPTIVE_%' THEN 'Preemptive'
WHEN ws.[wait_type] LIKE 'BROKER[_]%' THEN 'Service Broker'
WHEN ws.[wait_type] IN (
'WRITELOG','LOGBUFFER','LOGMGR_RESERVE_APPEND',
'LOGMGR_FLUSH', 'LOGMGR_PMM_LOG') THEN 'Tran Log IO'
WHEN ws.[wait_type] LIKE 'LOG_RATE%' then 'Log Rate Governor'
WHEN ws.[wait_type] LIKE 'HADR_THROTTLE[_]%'
or ws.[wait_type] = 'THROTTLE_LOG_RATE_LOG_STORAGE' THEN 'HADR Log Rate Governor'
WHEN ws.[wait_type] LIKE 'RBIO_RG%' or ws.[wait_type] like 'WAIT_RBIO_RG%' then 'VLDB Log Rate Governor'
WHEN ws.[wait_type] LIKE 'RBIO[_]%' or ws.[wait_type] like 'WAIT_RBIO[_]%' then 'VLDB RBIO'
WHEN ws.[wait_type] IN(
'ASYNC_NETWORK_IO','EXTERNAL_SCRIPT_NETWORK_IOF',
'NET_WAITFOR_PACKET','PROXY_NETWORK_IO') THEN 'Network IO'
WHEN ws.[wait_type] IN ( 'CXPACKET', 'CXCONSUMER')
or ws.[wait_type] like 'HT%' or ws.[wait_type] like 'BMP%'
or ws.[wait_type] like 'BP%' THEN 'Parallelism'
WHEN ws.[wait_type] IN(
'CMEMTHREAD','CMEMPARTITIONED','EE_PMOLOCK','EXCHANGE',
'RESOURCE_SEMAPHORE','MEMORY_ALLOCATION_EXT',
'RESERVED_MEMORY_ALLOCATION_EXT', 'MEMORY_GRANT_UPDATE') THEN 'Memory'
WHEN ws.[wait_type] IN ('WAITFOR','WAIT_FOR_RESULTS') THEN 'User Wait'
WHEN ws.[wait_type] LIKE 'HADR[_]%' or ws.[wait_type] LIKE 'PWAIT_HADR%'
or ws.[wait_type] LIKE 'REPLICA[_]%' or ws.[wait_type] LIKE 'REPL_%'
or ws.[wait_type] LIKE 'SE_REPL[_]%'
or ws.[wait_type] LIKE 'FCB_REPLICA%' THEN 'Replication'
WHEN ws.[wait_type] LIKE 'SQLTRACE[_]%'
or ws.[wait_type] IN (
'TRACEWRITE', 'SQLTRACE_LOCK', 'SQLTRACE_FILE_BUFFER', 'SQLTRACE_FILE_WRITE_IO_COMPLETION',
'SQLTRACE_FILE_READ_IO_COMPLETION', 'SQLTRACE_PENDING_BUFFER_WRITERS', 'SQLTRACE_SHUTDOWN',
'QUERY_TRACEOUT', 'TRACE_EVTNOTIF') THEN 'Tracing'
WHEN ws.[wait_type] IN (
'FT_RESTART_CRAWL', 'FULLTEXT GATHERER', 'MSSEARCH', 'FT_METADATA_MUTEX',
'FT_IFTSHC_MUTEX', 'FT_IFTSISM_MUTEX', 'FT_IFTS_RWLOCK', 'FT_COMPROWSET_RWLOCK',
'FT_MASTER_MERGE', 'FT_PROPERTYLIST_CACHE', 'FT_MASTER_MERGE_COORDINATOR',
'PWAIT_RESOURCE_SEMAPHORE_FT_PARALLEL_QUERY_SYNC') THEN 'Full Text Search'
ELSE 'Other'
END as [wait_category]
,DATABASEPROPERTYEX(DB_NAME(), 'Updateability') as replica_updateability
FROM sys.dm_os_wait_stats AS ws WITH (NOLOCK)
WHERE
ws.[wait_type] NOT IN (
N'BROKER_EVENTHANDLER', N'BROKER_RECEIVE_WAITFOR', N'BROKER_TASK_STOP',
N'BROKER_TO_FLUSH', N'BROKER_TRANSMITTER', N'CHECKPOINT_QUEUE',
N'CHKPT', N'CLR_AUTO_EVENT', N'CLR_MANUAL_EVENT', N'CLR_SEMAPHORE',
N'DBMIRROR_DBM_EVENT', N'DBMIRROR_EVENTS_QUEUE', N'DBMIRROR_QUEUE',
N'DBMIRRORING_CMD', N'DIRTY_PAGE_POLL', N'DISPATCHER_QUEUE_SEMAPHORE',
N'EXECSYNC', N'FSAGENT', N'FT_IFTS_SCHEDULER_IDLE_WAIT', N'FT_IFTSHC_MUTEX',
N'HADR_CLUSAPI_CALL', N'HADR_FILESTREAM_IOMGR_IOCOMPLETION', N'HADR_LOGCAPTURE_WAIT',
N'HADR_NOTIFICATION_DEQUEUE', N'HADR_TIMER_TASK', N'HADR_WORK_QUEUE',
N'KSOURCE_WAKEUP', N'LAZYWRITER_SLEEP', N'LOGMGR_QUEUE',
N'MEMORY_ALLOCATION_EXT', N'ONDEMAND_TASK_QUEUE',
N'PARALLEL_REDO_WORKER_WAIT_WORK',
N'PREEMPTIVE_HADR_LEASE_MECHANISM', N'PREEMPTIVE_SP_SERVER_DIAGNOSTICS',
N'PREEMPTIVE_OS_LIBRARYOPS', N'PREEMPTIVE_OS_COMOPS', N'PREEMPTIVE_OS_CRYPTOPS',
N'PREEMPTIVE_OS_PIPEOPS','PREEMPTIVE_OS_GENERICOPS', N'PREEMPTIVE_OS_VERIFYTRUST',
N'PREEMPTIVE_OS_DEVICEOPS',
N'PREEMPTIVE_XE_CALLBACKEXECUTE', N'PREEMPTIVE_XE_DISPATCHER',
N'PREEMPTIVE_XE_GETTARGETSTATE', N'PREEMPTIVE_XE_SESSIONCOMMIT',
N'PREEMPTIVE_XE_TARGETINIT', N'PREEMPTIVE_XE_TARGETFINALIZE',
N'PWAIT_ALL_COMPONENTS_INITIALIZED', N'PWAIT_DIRECTLOGCONSUMER_GETNEXT',
N'QDS_PERSIST_TASK_MAIN_LOOP_SLEEP',
N'QDS_ASYNC_QUEUE',
N'QDS_CLEANUP_STALE_QUERIES_TASK_MAIN_LOOP_SLEEP', N'REQUEST_FOR_DEADLOCK_SEARCH',
N'RESOURCE_QUEUE', N'SERVER_IDLE_CHECK', N'SLEEP_BPOOL_FLUSH', N'SLEEP_DBSTARTUP',
N'SLEEP_DCOMSTARTUP', N'SLEEP_MASTERDBREADY', N'SLEEP_MASTERMDREADY',
N'SLEEP_MASTERUPGRADED', N'SLEEP_MSDBSTARTUP', N'SLEEP_SYSTEMTASK', N'SLEEP_TASK',
N'SLEEP_TEMPDBSTARTUP', N'SNI_HTTP_ACCEPT', N'SP_SERVER_DIAGNOSTICS_SLEEP',
N'SQLTRACE_BUFFER_FLUSH', N'SQLTRACE_INCREMENTAL_FLUSH_SLEEP',
N'SQLTRACE_WAIT_ENTRIES',
N'WAIT_FOR_RESULTS', N'WAITFOR', N'WAITFOR_TASKSHUTDOWN', N'WAIT_XTP_HOST_WAIT',
N'WAIT_XTP_OFFLINE_CKPT_NEW_LOG', N'WAIT_XTP_CKPT_CLOSE',
N'XE_BUFFERMGR_ALLPROCESSED_EVENT', N'XE_DISPATCHER_JOIN',
N'XE_DISPATCHER_WAIT', N'XE_LIVE_TARGET_TVF', N'XE_TIMER_EVENT',
N'SOS_WORK_DISPATCHER','RESERVED_MEMORY_ALLOCATION_EXT','SQLTRACE_WAIT_ENTRIES',
N'RBIO_COMM_RETRY')
AND [waiting_tasks_count] > 10
AND [wait_time_ms] > 100;
`
const sqlAzureMIPerformanceCounters = `
SET DEADLOCK_PRIORITY -10;
IF SERVERPROPERTY('EngineEdition') <> 8 BEGIN /*not Azure Managed Instance*/
DECLARE @ErrorMessage AS nvarchar(500) = 'Telegraf - Connection string Server:'+ @@SERVERNAME + ',Database:' + DB_NAME() +' is not an Azure Managed Instance. Check the database_type parameter in the telegraf configuration.';
RAISERROR (@ErrorMessage,11,1)
RETURN
END
DECLARE @PCounters TABLE
(
[object_name] nvarchar(128),
[counter_name] nvarchar(128),
[instance_name] nvarchar(128),
[cntr_value] bigint,
[cntr_type] INT ,
Primary Key([object_name],[counter_name],[instance_name])
);
WITH PerfCounters AS (
SELECT DISTINCT
RTrim(spi.[object_name]) [object_name]
,RTrim(spi.[counter_name]) [counter_name]
,CASE WHEN (
RTRIM(spi.[object_name]) LIKE '%:Databases'
OR RTRIM(spi.[object_name]) LIKE '%:Database Replica'
OR RTRIM(spi.[object_name]) LIKE '%:Catalog Metadata'
OR RTRIM(spi.[object_name]) LIKE '%:Query Store'
OR RTRIM(spi.[object_name]) LIKE '%:Columnstore'
OR RTRIM(spi.[object_name]) LIKE '%:Advanced Analytics')
AND TRY_CONVERT([uniqueidentifier], spi.[instance_name]) IS NOT NULL -- for cloud only
THEN ISNULL(d.[name],RTRIM(spi.instance_name)) -- Elastic Pools counters exist for all databases but sys.databases only has current DB value
WHEN
RTRIM([object_name]) LIKE '%:Availability Replica'
AND TRY_CONVERT([uniqueidentifier], spi.[instance_name]) IS NOT NULL -- for cloud only
THEN ISNULL(d.[name],RTRIM(spi.[instance_name])) + RTRIM(SUBSTRING(spi.[instance_name], 37, LEN(spi.[instance_name])))
ELSE RTRIM(spi.instance_name)
END AS [instance_name]
,CAST(spi.[cntr_value] AS BIGINT) AS [cntr_value]
,spi.[cntr_type]
FROM sys.dm_os_performance_counters AS spi
LEFT JOIN sys.databases AS d
ON LEFT(spi.[instance_name], 36) -- some instance_name values have an additional identifier appended after the GUID
= CASE
/*in SQL DB standalone, physical_database_name for master is the GUID of the user database*/
WHEN d.[name] = 'master' AND TRY_CONVERT([uniqueidentifier], d.[physical_database_name]) IS NOT NULL
THEN d.[name]
ELSE d.[physical_database_name]
END
WHERE
counter_name IN (
'SQL Compilations/sec'
,'SQL Re-Compilations/sec'
,'User Connections'
,'Batch Requests/sec'
,'Logouts/sec'
,'Logins/sec'
,'Processes blocked'
,'Latch Waits/sec'
,'Full Scans/sec'
,'Index Searches/sec'
,'Page Splits/sec'
,'Page lookups/sec'
,'Page reads/sec'
,'Page writes/sec'
,'Readahead pages/sec'
,'Lazy writes/sec'
,'Checkpoint pages/sec'
,'Table Lock Escalations/sec'
,'Page life expectancy'
,'Log File(s) Size (KB)'
,'Log File(s) Used Size (KB)'
,'Data File(s) Size (KB)'
,'Transactions/sec'
,'Write Transactions/sec'
,'Active Transactions'
,'Log Growths'
,'Active Temp Tables'
,'Logical Connections'
,'Temp Tables Creation Rate'
,'Temp Tables For Destruction'
,'Free Space in tempdb (KB)'
,'Version Store Size (KB)'
,'Memory Grants Pending'
,'Memory Grants Outstanding'
,'Free list stalls/sec'
,'Buffer cache hit ratio'
,'Buffer cache hit ratio base'
,'Backup/Restore Throughput/sec'
,'Total Server Memory (KB)'
,'Target Server Memory (KB)'
,'Log Flushes/sec'
,'Log Flush Wait Time'
,'Memory broker clerk size'
,'Log Bytes Flushed/sec'
,'Bytes Sent to Replica/sec'
,'Log Send Queue'
,'Bytes Sent to Transport/sec'
,'Sends to Replica/sec'
,'Bytes Sent to Transport/sec'
,'Sends to Transport/sec'
,'Bytes Received from Replica/sec'
,'Receives from Replica/sec'
,'Flow Control Time (ms/sec)'
,'Flow Control/sec'
,'Resent Messages/sec'
,'Redone Bytes/sec'
,'XTP Memory Used (KB)'
,'Transaction Delay'
,'Log Bytes Received/sec'
,'Log Apply Pending Queue'
,'Redone Bytes/sec'
,'Recovery Queue'
,'Log Apply Ready Queue'
,'CPU usage %'
,'CPU usage % base'
,'Queued requests'
,'Requests completed/sec'
,'Blocked tasks'
,'Active memory grant amount (KB)'
,'Disk Read Bytes/sec'
,'Disk Read IO Throttled/sec'
,'Disk Read IO/sec'
,'Disk Write Bytes/sec'
,'Disk Write IO Throttled/sec'
,'Disk Write IO/sec'
,'Used memory (KB)'
,'Forwarded Records/sec'
,'Background Writer pages/sec'
,'Percent Log Used'
,'Log Send Queue KB'
,'Redo Queue KB'
,'Mirrored Write Transactions/sec'
,'Group Commit Time'
,'Group Commits/Sec'
,'Workfiles Created/sec'
,'Worktables Created/sec'
,'Distributed Query'
,'DTC calls'
,'Query Store CPU usage'
) OR (
spi.[object_name] LIKE '%User Settable%'
OR spi.[object_name] LIKE '%SQL Errors%'
OR spi.[object_name] LIKE '%Batch Resp Statistics%'
) OR (
spi.[instance_name] IN ('_Total')
AND spi.[counter_name] IN (
'Lock Timeouts/sec'
,'Lock Timeouts (timeout > 0)/sec'
,'Number of Deadlocks/sec'
,'Lock Waits/sec'
,'Latch Waits/sec'
)
)
)
INSERT INTO @PCounters select * from PerfCounters
SELECT
'sqlserver_performance' AS [measurement]
,REPLACE(@@SERVERNAME,'\',':') AS [sql_instance]
,pc.[object_name] AS [object]
,pc.[counter_name] AS [counter]
,CASE pc.[instance_name]
WHEN '_Total' THEN 'Total'
ELSE ISNULL(pc.[instance_name],'')
END AS [instance]
,CAST(CASE WHEN pc.[cntr_type] = 537003264 AND pc1.[cntr_value] > 0 THEN (pc.[cntr_value] * 1.0) / (pc1.[cntr_value] * 1.0) * 100 ELSE pc.[cntr_value] END AS float(10)) AS [value]
,cast(pc.[cntr_type] as varchar(25)) as [counter_type]
,DATABASEPROPERTYEX(DB_NAME(), 'Updateability') as replica_updateability
from @PCounters pc
LEFT OUTER JOIN @PCounters AS pc1
ON (
pc.[counter_name] = REPLACE(pc1.[counter_name],' base','')
OR pc.[counter_name] = REPLACE(pc1.[counter_name],' base',' (ms)')
)
AND pc.[object_name] = pc1.[object_name]
AND pc.[instance_name] = pc1.[instance_name]
AND pc1.[counter_name] LIKE '%base'
WHERE
pc.[counter_name] NOT LIKE '% base'
OPTION (RECOMPILE);
`
const sqlAzureMIRequests string = `
IF SERVERPROPERTY('EngineEdition') <> 8 BEGIN /*not Azure Managed Instance*/
DECLARE @ErrorMessage AS nvarchar(500) = 'Telegraf - Connection string Server:'+ @@SERVERNAME + ',Database:' + DB_NAME() +' is not an Azure Managed Instance. Check the database_type parameter in the telegraf configuration.';
RAISERROR (@ErrorMessage,11,1)
RETURN
END
BEGIN TRY
SELECT
[measurement],[sql_instance],[database_name],[session_id]
,ISNULL([request_id],0) AS [request_id]
,[blocking_session_id],[status],[cpu_time_ms]
,[total_elapsed_time_ms],[logical_reads],[writes]
,[command],[wait_time_ms],[wait_type]
,[wait_resource],[program_name]
,[host_name],[nt_user_name],[login_name]
,[transaction_isolation_level],[granted_query_memory_pages],[percent_complete]
,[statement_text],[objectid],[stmt_object_name]
,[stmt_db_name],[query_hash],[query_plan_hash]
,replica_updateability
,[session_db_name],[open_transaction]
FROM (
SELECT
'sqlserver_requests' AS [measurement]
,REPLACE(@@SERVERNAME,'\',':') AS [sql_instance]
,DB_NAME() as [database_name]
,s.[session_id]
,r.[request_id]
,DB_NAME(COALESCE(r.[database_id], s.[database_id])) AS [session_db_name]
,COALESCE(r.[status], s.[status]) AS [status]
,COALESCE(r.[cpu_time], s.[cpu_time]) AS [cpu_time_ms]
,COALESCE(r.[total_elapsed_time], s.[total_elapsed_time]) AS [total_elapsed_time_ms]
,COALESCE(r.[logical_reads], s.[logical_reads]) AS [logical_reads]
,COALESCE(r.[writes], s.[writes]) AS [writes]
,r.[command]
,r.[wait_time] AS [wait_time_ms]
,r.[wait_type]
,r.[wait_resource]
,NULLIF(r.[blocking_session_id],0) AS [blocking_session_id]
,s.[program_name]
,s.[host_name]
,s.[nt_user_name]
,s.[login_name]
,COALESCE(r.[open_transaction_count], s.[open_transaction_count]) AS [open_transaction]
,LEFT (CASE COALESCE(r.[transaction_isolation_level], s.[transaction_isolation_level])
WHEN 0 THEN '0-Read Committed'
WHEN 1 THEN '1-Read Uncommitted (NOLOCK)'
WHEN 2 THEN '2-Read Committed'
WHEN 3 THEN '3-Repeatable Read'
WHEN 4 THEN '4-Serializable'
WHEN 5 THEN '5-Snapshot'
ELSE CONVERT (varchar(30), r.[transaction_isolation_level]) + '-UNKNOWN'
END, 30) AS [transaction_isolation_level]
,r.[granted_query_memory] AS [granted_query_memory_pages]
,r.[percent_complete]
,SUBSTRING(
qt.[text],
r.[statement_start_offset] / 2 + 1,
(CASE WHEN r.[statement_end_offset] = -1
THEN DATALENGTH(qt.[text])
ELSE r.[statement_end_offset]
END - r.[statement_start_offset]) / 2 + 1
) AS [statement_text]
,qt.[objectid]
,QUOTENAME(OBJECT_SCHEMA_NAME(qt.[objectid], qt.[dbid])) + '.' + QUOTENAME(OBJECT_NAME(qt.[objectid], qt.[dbid])) as [stmt_object_name]
,DB_NAME(qt.[dbid]) AS [stmt_db_name]
,CONVERT(varchar(20),r.[query_hash],1) AS [query_hash]
,CONVERT(varchar(20),r.[query_plan_hash],1) AS [query_plan_hash]
,DATABASEPROPERTYEX(DB_NAME(), 'Updateability') as replica_updateability
,s.[is_user_process]
,[blocking_or_blocked] = COUNT(*) OVER(PARTITION BY ISNULL(NULLIF(r.[blocking_session_id], 0),s.[session_id]))
FROM sys.dm_exec_sessions AS s
LEFT OUTER JOIN sys.dm_exec_requests AS r
ON s.[session_id] = r.[session_id]
OUTER APPLY sys.dm_exec_sql_text(r.[sql_handle]) AS qt
) AS data
WHERE
[blocking_or_blocked] > 1 --Always include blocking or blocked sessions/requests
OR [open_transaction] >= 1 --Always include sessions with open transactions
OR (
[request_id] IS NOT NULL --A request must exists
AND ( --Always fetch user process (in any state), fetch system process only if active
[is_user_process] = 1
OR [status] COLLATE Latin1_General_BIN NOT IN ('background', 'sleeping')
)
AND [session_id] <> @@SPID
)
OPTION(MAXDOP 1);
END TRY
BEGIN CATCH
IF (ERROR_NUMBER() <> 976) --Avoid possible errors from secondary replica
THROW;
END CATCH
`
const sqlAzureMISchedulers string = `
IF SERVERPROPERTY('EngineEdition') <> 8 BEGIN /*not Azure Managed Instance*/
DECLARE @ErrorMessage AS nvarchar(500) = 'Telegraf - Connection string Server:'+ @@SERVERNAME + ',Database:' + DB_NAME() +' is not an Azure Managed Instance. Check the database_type parameter in the telegraf configuration.';
RAISERROR (@ErrorMessage,11,1)
RETURN
END
SELECT
'sqlserver_schedulers' AS [measurement]
,REPLACE(@@SERVERNAME, '\', ':') AS [sql_instance]
,CAST(s.[scheduler_id] AS VARCHAR(4)) AS [scheduler_id]
,CAST(s.[cpu_id] AS VARCHAR(4)) AS [cpu_id]
,s.[is_online]
,s.[is_idle]
,s.[preemptive_switches_count]
,s.[context_switches_count]
,s.[current_tasks_count]
,s.[runnable_tasks_count]
,s.[current_workers_count]
,s.[active_workers_count]
,s.[work_queue_count]
,s.[pending_disk_io_count]
,s.[load_factor]
,s.[yield_count]
,s.[total_cpu_usage_ms]
,s.[total_scheduler_delay_ms]
,DATABASEPROPERTYEX(DB_NAME(), 'Updateability') as replica_updateability
FROM sys.dm_os_schedulers AS s
`

View file

@ -0,0 +1,391 @@
package sqlserver
import (
"os"
"testing"
"github.com/stretchr/testify/require"
"github.com/influxdata/telegraf/config"
"github.com/influxdata/telegraf/testutil"
)
func TestAzureSQLIntegration_Managed_ResourceStats_Query(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
if os.Getenv("AZURESQL_MI_CONNECTION_STRING") == "" {
t.Skip("Missing environment variable AZURESQL_MI_CONNECTION_STRING")
}
connectionString := os.Getenv("AZURESQL_MI_CONNECTION_STRING")
sl := config.NewSecret([]byte(connectionString))
server := &SQLServer{
Servers: []*config.Secret{&sl},
IncludeQuery: []string{"AzureSQLMIResourceStats"},
AuthMethod: "connection_string",
DatabaseType: "AzureSQLManagedInstance",
}
var acc testutil.Accumulator
require.NoError(t, server.Start(&acc))
require.NoError(t, server.Gather(&acc))
require.True(t, acc.HasMeasurement("sqlserver_azure_db_resource_stats"))
require.True(t, acc.HasTag("sqlserver_azure_db_resource_stats", "sql_instance"))
require.True(t, acc.HasFloatField("sqlserver_azure_db_resource_stats", "avg_cpu_percent"))
require.True(t, acc.HasTag("sqlserver_azure_db_resource_stats", "replica_updateability"))
// This query should only return one row
require.Len(t, acc.Metrics, 1)
server.Stop()
}
func TestAzureSQLIntegration_Managed_ResourceGovernance_Query(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
if os.Getenv("AZURESQL_MI_CONNECTION_STRING") == "" {
t.Skip("Missing environment variable AZURESQL_MI_CONNECTION_STRING")
}
connectionString := os.Getenv("AZURESQL_MI_CONNECTION_STRING")
sl := config.NewSecret([]byte(connectionString))
server := &SQLServer{
Servers: []*config.Secret{&sl},
IncludeQuery: []string{"AzureSQLMIResourceGovernance"},
AuthMethod: "connection_string",
DatabaseType: "AzureSQLManagedInstance",
}
var acc testutil.Accumulator
require.NoError(t, server.Start(&acc))
require.NoError(t, server.Gather(&acc))
require.True(t, acc.HasMeasurement("sqlserver_instance_resource_governance"))
require.True(t, acc.HasTag("sqlserver_instance_resource_governance", "sql_instance"))
require.True(t, acc.HasInt64Field("sqlserver_instance_resource_governance", "instance_cap_cpu"))
require.True(t, acc.HasInt64Field("sqlserver_instance_resource_governance", "instance_max_log_rate"))
require.True(t, acc.HasInt64Field("sqlserver_instance_resource_governance", "instance_max_worker_threads"))
require.True(t, acc.HasInt64Field("sqlserver_instance_resource_governance", "tempdb_log_file_number"))
require.True(t, acc.HasInt64Field("sqlserver_instance_resource_governance", "volume_local_iops"))
require.True(t, acc.HasInt64Field("sqlserver_instance_resource_governance", "volume_external_xstore_iops"))
require.True(t, acc.HasInt64Field("sqlserver_instance_resource_governance", "volume_managed_xstore_iops"))
require.True(t, acc.HasInt64Field("sqlserver_instance_resource_governance", "voltype_local_iops"))
require.True(t, acc.HasInt64Field("sqlserver_instance_resource_governance", "voltype_man_xtore_iops"))
require.True(t, acc.HasInt64Field("sqlserver_instance_resource_governance", "voltype_ext_xtore_iops"))
require.True(t, acc.HasInt64Field("sqlserver_instance_resource_governance", "vol_ext_xtore_iops"))
require.True(t, acc.HasTag("sqlserver_instance_resource_governance", "replica_updateability"))
server.Stop()
}
func TestAzureSQLIntegration_Managed_DatabaseIO_Query(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
if os.Getenv("AZURESQL_MI_CONNECTION_STRING") == "" {
t.Skip("Missing environment variable AZURESQL_MI_CONNECTION_STRING")
}
connectionString := os.Getenv("AZURESQL_MI_CONNECTION_STRING")
sl := config.NewSecret([]byte(connectionString))
server := &SQLServer{
Servers: []*config.Secret{&sl},
IncludeQuery: []string{"AzureSQLMIDatabaseIO"},
AuthMethod: "connection_string",
DatabaseType: "AzureSQLManagedInstance",
}
var acc testutil.Accumulator
require.NoError(t, server.Start(&acc))
require.NoError(t, server.Gather(&acc))
require.True(t, acc.HasMeasurement("sqlserver_database_io"))
require.True(t, acc.HasTag("sqlserver_database_io", "sql_instance"))
require.True(t, acc.HasTag("sqlserver_database_io", "database_name"))
require.True(t, acc.HasTag("sqlserver_database_io", "physical_filename"))
require.True(t, acc.HasTag("sqlserver_database_io", "logical_filename"))
require.True(t, acc.HasTag("sqlserver_database_io", "file_type"))
require.True(t, acc.HasInt64Field("sqlserver_database_io", "reads"))
require.True(t, acc.HasInt64Field("sqlserver_database_io", "read_bytes"))
require.True(t, acc.HasInt64Field("sqlserver_database_io", "read_latency_ms"))
require.True(t, acc.HasInt64Field("sqlserver_database_io", "write_latency_ms"))
require.True(t, acc.HasInt64Field("sqlserver_database_io", "writes"))
require.True(t, acc.HasInt64Field("sqlserver_database_io", "write_bytes"))
require.True(t, acc.HasInt64Field("sqlserver_database_io", "rg_read_stall_ms"))
require.True(t, acc.HasInt64Field("sqlserver_database_io", "rg_write_stall_ms"))
require.True(t, acc.HasTag("sqlserver_database_io", "replica_updateability"))
server.Stop()
}
func TestAzureSQLIntegration_Managed_ServerProperties_Query(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
if os.Getenv("AZURESQL_MI_CONNECTION_STRING") == "" {
t.Skip("Missing environment variable AZURESQL_MI_CONNECTION_STRING")
}
connectionString := os.Getenv("AZURESQL_MI_CONNECTION_STRING")
sl := config.NewSecret([]byte(connectionString))
server := &SQLServer{
Servers: []*config.Secret{&sl},
IncludeQuery: []string{"AzureSQLMIServerProperties"},
AuthMethod: "connection_string",
DatabaseType: "AzureSQLManagedInstance",
}
var acc testutil.Accumulator
require.NoError(t, server.Start(&acc))
require.NoError(t, server.Gather(&acc))
require.True(t, acc.HasMeasurement("sqlserver_server_properties"))
require.True(t, acc.HasTag("sqlserver_server_properties", "sql_instance"))
require.True(t, acc.HasInt64Field("sqlserver_server_properties", "cpu_count"))
require.True(t, acc.HasInt64Field("sqlserver_server_properties", "server_memory"))
require.True(t, acc.HasTag("sqlserver_server_properties", "sku"))
require.True(t, acc.HasInt64Field("sqlserver_server_properties", "engine_edition"))
require.True(t, acc.HasTag("sqlserver_server_properties", "hardware_type"))
require.True(t, acc.HasInt64Field("sqlserver_server_properties", "total_storage_mb"))
require.True(t, acc.HasInt64Field("sqlserver_server_properties", "available_storage_mb"))
require.True(t, acc.HasField("sqlserver_server_properties", "uptime")) // Time field.
require.True(t, acc.HasTag("sqlserver_server_properties", "sql_version"))
require.True(t, acc.HasTag("sqlserver_server_properties", "sql_version_desc"))
require.True(t, acc.HasInt64Field("sqlserver_server_properties", "db_online"))
require.True(t, acc.HasInt64Field("sqlserver_server_properties", "db_restoring"))
require.True(t, acc.HasInt64Field("sqlserver_server_properties", "db_recovering"))
require.True(t, acc.HasInt64Field("sqlserver_server_properties", "db_recoveryPending"))
require.True(t, acc.HasInt64Field("sqlserver_server_properties", "db_suspect"))
require.True(t, acc.HasTag("sqlserver_server_properties", "replica_updateability"))
// This query should only return one row
require.Len(t, acc.Metrics, 1)
server.Stop()
}
func TestAzureSQLIntegration_Managed_OsWaitStats_Query(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
if os.Getenv("AZURESQL_MI_CONNECTION_STRING") == "" {
t.Skip("Missing environment variable AZURESQL_MI_CONNECTION_STRING")
}
connectionString := os.Getenv("AZURESQL_MI_CONNECTION_STRING")
sl := config.NewSecret([]byte(connectionString))
server := &SQLServer{
Servers: []*config.Secret{&sl},
IncludeQuery: []string{"AzureSQLMIOsWaitstats"},
AuthMethod: "connection_string",
DatabaseType: "AzureSQLManagedInstance",
}
var acc testutil.Accumulator
require.NoError(t, server.Start(&acc))
require.NoError(t, server.Gather(&acc))
require.True(t, acc.HasMeasurement("sqlserver_waitstats"))
require.True(t, acc.HasTag("sqlserver_waitstats", "sql_instance"))
require.True(t, acc.HasTag("sqlserver_waitstats", "wait_type"))
require.True(t, acc.HasInt64Field("sqlserver_waitstats", "waiting_tasks_count"))
require.True(t, acc.HasInt64Field("sqlserver_waitstats", "wait_time_ms"))
require.True(t, acc.HasInt64Field("sqlserver_waitstats", "max_wait_time_ms"))
require.True(t, acc.HasInt64Field("sqlserver_waitstats", "signal_wait_time_ms"))
require.True(t, acc.HasInt64Field("sqlserver_waitstats", "resource_wait_ms"))
require.True(t, acc.HasTag("sqlserver_waitstats", "wait_category"))
require.True(t, acc.HasTag("sqlserver_waitstats", "replica_updateability"))
server.Stop()
}
func TestAzureSQLIntegration_Managed_MemoryClerks_Query(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
if os.Getenv("AZURESQL_MI_CONNECTION_STRING") == "" {
t.Skip("Missing environment variable AZURESQL_MI_CONNECTION_STRING")
}
connectionString := os.Getenv("AZURESQL_MI_CONNECTION_STRING")
sl := config.NewSecret([]byte(connectionString))
server := &SQLServer{
Servers: []*config.Secret{&sl},
IncludeQuery: []string{"AzureSQLMIMemoryClerks"},
AuthMethod: "connection_string",
DatabaseType: "AzureSQLManagedInstance",
}
var acc testutil.Accumulator
require.NoError(t, server.Start(&acc))
require.NoError(t, server.Gather(&acc))
require.True(t, acc.HasMeasurement("sqlserver_memory_clerks"))
require.True(t, acc.HasTag("sqlserver_memory_clerks", "sql_instance"))
require.True(t, acc.HasTag("sqlserver_memory_clerks", "clerk_type"))
require.True(t, acc.HasInt64Field("sqlserver_memory_clerks", "size_kb"))
require.True(t, acc.HasTag("sqlserver_memory_clerks", "replica_updateability"))
server.Stop()
}
func TestAzureSQLIntegration_Managed_PerformanceCounters_Query(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
if os.Getenv("AZURESQL_MI_CONNECTION_STRING") == "" {
t.Skip("Missing environment variable AZURESQL_MI_CONNECTION_STRING")
}
connectionString := os.Getenv("AZURESQL_MI_CONNECTION_STRING")
sl := config.NewSecret([]byte(connectionString))
server := &SQLServer{
Servers: []*config.Secret{&sl},
IncludeQuery: []string{"AzureSQLMIPerformanceCounters"},
AuthMethod: "connection_string",
DatabaseType: "AzureSQLManagedInstance",
}
var acc testutil.Accumulator
require.NoError(t, server.Start(&acc))
require.NoError(t, server.Gather(&acc))
require.True(t, acc.HasMeasurement("sqlserver_performance"))
require.True(t, acc.HasTag("sqlserver_performance", "sql_instance"))
require.True(t, acc.HasTag("sqlserver_performance", "object"))
require.True(t, acc.HasTag("sqlserver_performance", "counter"))
require.True(t, acc.HasTag("sqlserver_performance", "instance"))
require.True(t, acc.HasFloatField("sqlserver_performance", "value"))
require.True(t, acc.HasTag("sqlserver_performance", "counter_type"))
require.True(t, acc.HasTag("sqlserver_performance", "replica_updateability"))
server.Stop()
}
func TestAzureSQLIntegration_Managed_Requests_Query(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
if os.Getenv("AZURESQL_MI_CONNECTION_STRING") == "" {
t.Skip("Missing environment variable AZURESQL_MI_CONNECTION_STRING")
}
connectionString := os.Getenv("AZURESQL_MI_CONNECTION_STRING")
sl := config.NewSecret([]byte(connectionString))
server := &SQLServer{
Servers: []*config.Secret{&sl},
IncludeQuery: []string{"AzureSQLMIRequests"},
AuthMethod: "connection_string",
DatabaseType: "AzureSQLManagedInstance",
}
var acc testutil.Accumulator
require.NoError(t, server.Start(&acc))
require.NoError(t, server.Gather(&acc))
require.True(t, acc.HasMeasurement("sqlserver_requests"))
require.True(t, acc.HasTag("sqlserver_requests", "sql_instance"))
require.True(t, acc.HasTag("sqlserver_requests", "database_name"))
require.True(t, acc.HasInt64Field("sqlserver_requests", "session_id"))
require.True(t, acc.HasInt64Field("sqlserver_requests", "request_id"))
require.True(t, acc.HasTag("sqlserver_requests", "status"))
require.True(t, acc.HasInt64Field("sqlserver_requests", "cpu_time_ms"))
require.True(t, acc.HasInt64Field("sqlserver_requests", "total_elapsed_time_ms"))
require.True(t, acc.HasInt64Field("sqlserver_requests", "logical_reads"))
require.True(t, acc.HasInt64Field("sqlserver_requests", "writes"))
require.True(t, acc.HasTag("sqlserver_requests", "command"))
require.True(t, acc.HasInt64Field("sqlserver_requests", "wait_time_ms"))
require.True(t, acc.HasTag("sqlserver_requests", "wait_type"))
require.True(t, acc.HasTag("sqlserver_requests", "wait_resource"))
require.True(t, acc.HasInt64Field("sqlserver_requests", "blocking_session_id"))
require.True(t, acc.HasTag("sqlserver_requests", "program_name"))
require.True(t, acc.HasTag("sqlserver_requests", "host_name"))
require.True(t, acc.HasTag("sqlserver_requests", "nt_user_name"))
require.True(t, acc.HasTag("sqlserver_requests", "login_name"))
require.True(t, acc.HasInt64Field("sqlserver_requests", "open_transaction"))
require.True(t, acc.HasTag("sqlserver_requests", "transaction_isolation_level"))
require.True(t, acc.HasInt64Field("sqlserver_requests", "granted_query_memory_pages"))
require.True(t, acc.HasFloatField("sqlserver_requests", "percent_complete"))
require.True(t, acc.HasTag("sqlserver_requests", "statement_text"))
require.True(t, acc.HasField("sqlserver_requests", "objectid")) // Can be null.
require.True(t, acc.HasField("sqlserver_requests", "stmt_object_name")) // Can be null.
require.True(t, acc.HasField("sqlserver_requests", "stmt_db_name")) // Can be null.
require.True(t, acc.HasTag("sqlserver_requests", "query_hash"))
require.True(t, acc.HasTag("sqlserver_requests", "query_plan_hash"))
require.True(t, acc.HasTag("sqlserver_requests", "session_db_name"))
require.True(t, acc.HasTag("sqlserver_requests", "replica_updateability"))
server.Stop()
}
func TestAzureSQLIntegration_Managed_Schedulers_Query(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
if os.Getenv("AZURESQL_MI_CONNECTION_STRING") == "" {
t.Skip("Missing environment variable AZURESQL_MI_CONNECTION_STRING")
}
connectionString := os.Getenv("AZURESQL_MI_CONNECTION_STRING")
sl := config.NewSecret([]byte(connectionString))
server := &SQLServer{
Servers: []*config.Secret{&sl},
IncludeQuery: []string{"AzureSQLMISchedulers"},
AuthMethod: "connection_string",
DatabaseType: "AzureSQLManagedInstance",
}
var acc testutil.Accumulator
require.NoError(t, server.Start(&acc))
require.NoError(t, server.Gather(&acc))
require.True(t, acc.HasMeasurement("sqlserver_schedulers"))
require.True(t, acc.HasTag("sqlserver_schedulers", "sql_instance"))
require.True(t, acc.HasTag("sqlserver_schedulers", "scheduler_id"))
require.True(t, acc.HasTag("sqlserver_schedulers", "cpu_id"))
require.True(t, acc.HasField("sqlserver_schedulers", "is_online")) // Bool field.
require.True(t, acc.HasField("sqlserver_schedulers", "is_idle")) // Bool field.
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "preemptive_switches_count"))
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "context_switches_count"))
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "current_tasks_count"))
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "runnable_tasks_count"))
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "current_workers_count"))
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "active_workers_count"))
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "work_queue_count"))
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "pending_disk_io_count"))
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "load_factor"))
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "yield_count"))
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "total_cpu_usage_ms"))
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "total_scheduler_delay_ms"))
require.True(t, acc.HasTag("sqlserver_schedulers", "replica_updateability"))
server.Stop()
}

View file

@ -0,0 +1,478 @@
//nolint:lll // conditionally long lines allowed
package sqlserver
import (
_ "github.com/microsoft/go-mssqldb" // go-mssqldb initialization
)
// ------------------------------------------------------------------------------------------------
// ------------------ Azure Sql Elastic Pool ------------------------------------------------------
// ------------------------------------------------------------------------------------------------
const sqlAzurePoolResourceStats = `
IF SERVERPROPERTY('EngineEdition') <> 5
OR NOT EXISTS (SELECT 1 FROM sys.database_service_objectives WHERE database_id = DB_ID() AND elastic_pool_name IS NOT NULL) BEGIN
DECLARE @ErrorMessage AS nvarchar(500) = 'Telegraf - Connection string Server:'+ @@SERVERNAME + ',Database:' + DB_NAME() +' is not an Azure SQL database in an elastic pool. Check the database_type parameter in the telegraf configuration.';
RAISERROR (@ErrorMessage,11,1)
RETURN
END
SELECT TOP(1)
'sqlserver_pool_resource_stats' AS [measurement]
,REPLACE(@@SERVERNAME,'\',':') AS [sql_instance]
,(SELECT [elastic_pool_name] FROM sys.database_service_objectives WHERE database_id = DB_ID()) AS [elastic_pool_name]
,[snapshot_time]
,cast([cap_vcores_used_percent] as float) AS [avg_cpu_percent]
,cast([avg_data_io_percent] as float) AS [avg_data_io_percent]
,cast([avg_log_write_percent] as float) AS [avg_log_write_percent]
,cast([avg_storage_percent] as float) AS [avg_storage_percent]
,cast([max_worker_percent] as float) AS [max_worker_percent]
,cast([max_session_percent] as float) AS [max_session_percent]
,cast([max_data_space_kb]/1024. as int) AS [storage_limit_mb]
,cast([avg_instance_cpu_percent] as float) AS [avg_instance_cpu_percent]
,cast([avg_allocated_storage_percent] as float) AS [avg_allocated_storage_percent]
FROM
sys.dm_resource_governor_resource_pools_history_ex
WHERE
[name] = 'SloSharedPool1'
ORDER BY
[snapshot_time] DESC;
`
const sqlAzurePoolResourceGovernance = `
IF SERVERPROPERTY('EngineEdition') <> 5
OR NOT EXISTS (SELECT 1 FROM sys.database_service_objectives WHERE database_id = DB_ID() AND elastic_pool_name IS NOT NULL) BEGIN
DECLARE @ErrorMessage AS nvarchar(500) = 'Telegraf - Connection string Server:'+ @@SERVERNAME + ',Database:' + DB_NAME() +' is not an Azure SQL database in an elastic pool. Check the database_type parameter in the telegraf configuration.';
RAISERROR (@ErrorMessage,11,1)
RETURN
END
SELECT
'sqlserver_pool_resource_governance' AS [measurement]
,REPLACE(@@SERVERNAME,'\',':') AS [sql_instance]
,(SELECT [elastic_pool_name] FROM sys.database_service_objectives WHERE database_id = DB_ID()) AS [elastic_pool_name]
,[slo_name]
,[dtu_limit]
,[cpu_limit]
,[max_cpu]
,[cap_cpu]
,[max_db_memory]
,[max_db_max_size_in_mb]
,[db_file_growth_in_mb]
,[log_size_in_mb]
,[instance_cap_cpu]
,[instance_max_log_rate]
,[instance_max_worker_threads]
,[checkpoint_rate_mbps]
,[checkpoint_rate_io]
,[primary_group_max_workers]
,[primary_min_log_rate]
,[primary_max_log_rate]
,[primary_group_min_io]
,[primary_group_max_io]
,[primary_group_min_cpu]
,[primary_group_max_cpu]
,[primary_pool_max_workers]
,[pool_max_io]
,[volume_local_iops]
,[volume_managed_xstore_iops]
,[volume_external_xstore_iops]
,[volume_type_local_iops]
,[volume_type_managed_xstore_iops]
,[volume_type_external_xstore_iops]
,[volume_pfs_iops]
,[volume_type_pfs_iops]
FROM
sys.dm_user_db_resource_governance
WHERE database_id = DB_ID();
`
const sqlAzurePoolDatabaseIO = `
IF SERVERPROPERTY('EngineEdition') <> 5
OR NOT EXISTS (SELECT 1 FROM sys.database_service_objectives WHERE database_id = DB_ID() AND elastic_pool_name IS NOT NULL) BEGIN
DECLARE @ErrorMessage AS nvarchar(500) = 'Telegraf - Connection string Server:'+ @@SERVERNAME + ',Database:' + DB_NAME() +' is not an Azure SQL database in an elastic pool. Check the database_type parameter in the telegraf configuration.';
RAISERROR (@ErrorMessage,11,1)
RETURN
END
SELECT
'sqlserver_database_io' AS [measurement]
,REPLACE(@@SERVERNAME,'\',':') AS [sql_instance]
,(SELECT [elastic_pool_name] FROM sys.database_service_objectives WHERE database_id = DB_ID()) AS [elastic_pool_name]
,CASE
WHEN vfs.[database_id] = 1 THEN 'master'
WHEN vfs.[database_id] = 2 THEN 'tempdb'
WHEN vfs.[database_id] = 3 THEN 'model'
WHEN vfs.[database_id] = 4 THEN 'msdb'
ELSE gov.[database_name]
END AS [database_name]
,vfs.[database_id]
,vfs.[file_id]
,CASE
WHEN vfs.[file_id] = 2 THEN 'LOG'
ELSE 'ROWS'
END AS [file_type]
,vfs.[num_of_reads] AS [reads]
,vfs.[num_of_bytes_read] AS [read_bytes]
,vfs.[io_stall_read_ms] AS [read_latency_ms]
,vfs.[io_stall_write_ms] AS [write_latency_ms]
,vfs.[num_of_writes] AS [writes]
,vfs.[num_of_bytes_written] AS [write_bytes]
,vfs.[io_stall_queued_read_ms] AS [rg_read_stall_ms]
,vfs.[io_stall_queued_write_ms] AS [rg_write_stall_ms]
,[size_on_disk_bytes]
,ISNULL([size_on_disk_bytes],0)/(1024*1024) AS [size_on_disk_mb]
FROM
sys.dm_io_virtual_file_stats(NULL,NULL) AS vfs
LEFT OUTER JOIN
sys.dm_user_db_resource_governance AS gov
ON vfs.[database_id] = gov.[database_id];
`
const sqlAzurePoolOsWaitStats = `
IF SERVERPROPERTY('EngineEdition') <> 5
OR NOT EXISTS (SELECT 1 FROM sys.database_service_objectives WHERE database_id = DB_ID() AND elastic_pool_name IS NOT NULL) BEGIN
DECLARE @ErrorMessage AS nvarchar(500) = 'Telegraf - Connection string Server:'+ @@SERVERNAME + ',Database:' + DB_NAME() +' is not an Azure SQL database in an elastic pool. Check the database_type parameter in the telegraf configuration.';
RAISERROR (@ErrorMessage,11,1)
RETURN
END
SELECT
'sqlserver_waitstats' AS [measurement]
,REPLACE(@@SERVERNAME,'\',':') AS [sql_instance]
,(SELECT [elastic_pool_name] FROM sys.database_service_objectives WHERE database_id = DB_ID()) AS [elastic_pool_name]
,[wait_type]
,[waiting_tasks_count]
,[wait_time_ms]
,[max_wait_time_ms]
,[signal_wait_time_ms]
,[wait_time_ms]-[signal_wait_time_ms] AS [resource_wait_ms]
,CASE
WHEN ws.[wait_type] LIKE 'SOS_SCHEDULER_YIELD' THEN 'CPU'
WHEN ws.[wait_type] = 'THREADPOOL' THEN 'Worker Thread'
WHEN ws.[wait_type] LIKE 'LCK[_]%' THEN 'Lock'
WHEN ws.[wait_type] LIKE 'LATCH[_]%' THEN 'Latch'
WHEN ws.[wait_type] LIKE 'PAGELATCH[_]%' THEN 'Buffer Latch'
WHEN ws.[wait_type] LIKE 'PAGEIOLATCH[_]%' THEN 'Buffer IO'
WHEN ws.[wait_type] LIKE 'RESOURCE_SEMAPHORE_QUERY_COMPILE%' THEN 'Compilation'
WHEN ws.[wait_type] LIKE 'CLR[_]%' OR ws.[wait_type] LIKE 'SQLCLR%' THEN 'SQL CLR'
WHEN ws.[wait_type] LIKE 'DBMIRROR_%' THEN 'Mirroring'
WHEN ws.[wait_type] LIKE 'DTC[_]%' OR ws.[wait_type] LIKE 'DTCNEW%' OR ws.[wait_type] LIKE 'TRAN_%'
OR ws.[wait_type] LIKE 'XACT%' OR ws.[wait_type] LIKE 'MSQL_XACT%' THEN 'Transaction'
WHEN ws.[wait_type] LIKE 'SLEEP[_]%' OR ws.[wait_type] IN (
'LAZYWRITER_SLEEP', 'SQLTRACE_BUFFER_FLUSH', 'SQLTRACE_INCREMENTAL_FLUSH_SLEEP',
'SQLTRACE_WAIT_ENTRIES', 'FT_IFTS_SCHEDULER_IDLE_WAIT', 'XE_DISPATCHER_WAIT',
'REQUEST_FOR_DEADLOCK_SEARCH', 'LOGMGR_QUEUE', 'ONDEMAND_TASK_QUEUE',
'CHECKPOINT_QUEUE', 'XE_TIMER_EVENT') THEN 'Idle'
WHEN ws.[wait_type] IN (
'ASYNC_IO_COMPLETION','BACKUPIO','CHKPT','WRITE_COMPLETION',
'IO_QUEUE_LIMIT', 'IO_RETRY') THEN 'Other Disk IO'
WHEN ws.[wait_type] LIKE 'PREEMPTIVE_%' THEN 'Preemptive'
WHEN ws.[wait_type] LIKE 'BROKER[_]%' THEN 'Service Broker'
WHEN ws.[wait_type] IN (
'WRITELOG','LOGBUFFER','LOGMGR_RESERVE_APPEND',
'LOGMGR_FLUSH', 'LOGMGR_PMM_LOG') THEN 'Tran Log IO'
WHEN ws.[wait_type] LIKE 'LOG_RATE%' then 'Log Rate Governor'
WHEN ws.[wait_type] LIKE 'HADR_THROTTLE[_]%'
OR ws.[wait_type] = 'THROTTLE_LOG_RATE_LOG_STORAGE' THEN 'HADR Log Rate Governor'
WHEN ws.[wait_type] LIKE 'RBIO_RG%' OR ws.[wait_type] LIKE 'WAIT_RBIO_RG%' THEN 'VLDB Log Rate Governor'
WHEN ws.[wait_type] LIKE 'RBIO[_]%' OR ws.[wait_type] LIKE 'WAIT_RBIO[_]%' THEN 'VLDB RBIO'
WHEN ws.[wait_type] IN(
'ASYNC_NETWORK_IO','EXTERNAL_SCRIPT_NETWORK_IOF',
'NET_WAITFOR_PACKET','PROXY_NETWORK_IO') THEN 'Network IO'
WHEN ws.[wait_type] IN ( 'CXPACKET', 'CXCONSUMER')
OR ws.[wait_type] LIKE 'HT%' or ws.[wait_type] LIKE 'BMP%'
OR ws.[wait_type] LIKE 'BP%' THEN 'Parallelism'
WHEN ws.[wait_type] IN(
'CMEMTHREAD','CMEMPARTITIONED','EE_PMOLOCK','EXCHANGE',
'RESOURCE_SEMAPHORE','MEMORY_ALLOCATION_EXT',
'RESERVED_MEMORY_ALLOCATION_EXT', 'MEMORY_GRANT_UPDATE') THEN 'Memory'
WHEN ws.[wait_type] IN ('WAITFOR','WAIT_FOR_RESULTS') THEN 'User Wait'
WHEN ws.[wait_type] LIKE 'HADR[_]%' or ws.[wait_type] LIKE 'PWAIT_HADR%'
OR ws.[wait_type] LIKE 'REPLICA[_]%' or ws.[wait_type] LIKE 'REPL_%'
OR ws.[wait_type] LIKE 'SE_REPL[_]%'
OR ws.[wait_type] LIKE 'FCB_REPLICA%' THEN 'Replication'
WHEN ws.[wait_type] LIKE 'SQLTRACE[_]%'
OR ws.[wait_type] IN (
'TRACEWRITE', 'SQLTRACE_LOCK', 'SQLTRACE_FILE_BUFFER', 'SQLTRACE_FILE_WRITE_IO_COMPLETION',
'SQLTRACE_FILE_READ_IO_COMPLETION', 'SQLTRACE_PENDING_BUFFER_WRITERS', 'SQLTRACE_SHUTDOWN',
'QUERY_TRACEOUT', 'TRACE_EVTNOTIF') THEN 'Tracing'
WHEN ws.[wait_type] IN (
'FT_RESTART_CRAWL', 'FULLTEXT GATHERER', 'MSSEARCH', 'FT_METADATA_MUTEX',
'FT_IFTSHC_MUTEX', 'FT_IFTSISM_MUTEX', 'FT_IFTS_RWLOCK', 'FT_COMPROWSET_RWLOCK',
'FT_MASTER_MERGE', 'FT_PROPERTYLIST_CACHE', 'FT_MASTER_MERGE_COORDINATOR',
'PWAIT_RESOURCE_SEMAPHORE_FT_PARALLEL_QUERY_SYNC') THEN 'Full Text Search'
ELSE 'Other'
END AS [wait_category]
FROM sys.dm_os_wait_stats AS ws
WHERE
ws.[wait_type] NOT IN (
N'BROKER_EVENTHANDLER', N'BROKER_RECEIVE_WAITFOR', N'BROKER_TASK_STOP',
N'BROKER_TO_FLUSH', N'BROKER_TRANSMITTER', N'CHECKPOINT_QUEUE',
N'CHKPT', N'CLR_AUTO_EVENT', N'CLR_MANUAL_EVENT', N'CLR_SEMAPHORE',
N'DBMIRROR_DBM_EVENT', N'DBMIRROR_EVENTS_QUEUE', N'DBMIRROR_QUEUE',
N'DBMIRRORING_CMD', N'DIRTY_PAGE_POLL', N'DISPATCHER_QUEUE_SEMAPHORE',
N'EXECSYNC', N'FSAGENT', N'FT_IFTS_SCHEDULER_IDLE_WAIT', N'FT_IFTSHC_MUTEX',
N'HADR_CLUSAPI_CALL', N'HADR_FILESTREAM_IOMGR_IOCOMPLETION', N'HADR_LOGCAPTURE_WAIT',
N'HADR_NOTIFICATION_DEQUEUE', N'HADR_TIMER_TASK', N'HADR_WORK_QUEUE',
N'KSOURCE_WAKEUP', N'LAZYWRITER_SLEEP', N'LOGMGR_QUEUE',
N'MEMORY_ALLOCATION_EXT', N'ONDEMAND_TASK_QUEUE',
N'PARALLEL_REDO_WORKER_WAIT_WORK',
N'PREEMPTIVE_HADR_LEASE_MECHANISM', N'PREEMPTIVE_SP_SERVER_DIAGNOSTICS',
N'PREEMPTIVE_OS_LIBRARYOPS', N'PREEMPTIVE_OS_COMOPS', N'PREEMPTIVE_OS_CRYPTOPS',
N'PREEMPTIVE_OS_PIPEOPS','PREEMPTIVE_OS_GENERICOPS', N'PREEMPTIVE_OS_VERIFYTRUST',
N'PREEMPTIVE_OS_DEVICEOPS',
N'PREEMPTIVE_XE_CALLBACKEXECUTE', N'PREEMPTIVE_XE_DISPATCHER',
N'PREEMPTIVE_XE_GETTARGETSTATE', N'PREEMPTIVE_XE_SESSIONCOMMIT',
N'PREEMPTIVE_XE_TARGETINIT', N'PREEMPTIVE_XE_TARGETFINALIZE',
N'PWAIT_ALL_COMPONENTS_INITIALIZED', N'PWAIT_DIRECTLOGCONSUMER_GETNEXT',
N'QDS_PERSIST_TASK_MAIN_LOOP_SLEEP', N'QDS_ASYNC_QUEUE',
N'QDS_CLEANUP_STALE_QUERIES_TASK_MAIN_LOOP_SLEEP', N'REQUEST_FOR_DEADLOCK_SEARCH',
N'RESOURCE_QUEUE', N'SERVER_IDLE_CHECK', N'SLEEP_BPOOL_FLUSH', N'SLEEP_DBSTARTUP',
N'SLEEP_DCOMSTARTUP', N'SLEEP_MASTERDBREADY', N'SLEEP_MASTERMDREADY',
N'SLEEP_MASTERUPGRADED', N'SLEEP_MSDBSTARTUP', N'SLEEP_SYSTEMTASK', N'SLEEP_TASK',
N'SLEEP_TEMPDBSTARTUP', N'SNI_HTTP_ACCEPT', N'SP_SERVER_DIAGNOSTICS_SLEEP',
N'SQLTRACE_BUFFER_FLUSH', N'SQLTRACE_INCREMENTAL_FLUSH_SLEEP',
N'SQLTRACE_WAIT_ENTRIES', N'WAIT_FOR_RESULTS', N'WAITFOR', N'WAITFOR_TASKSHUTDOWN',
N'WAIT_XTP_HOST_WAIT', N'WAIT_XTP_OFFLINE_CKPT_NEW_LOG', N'WAIT_XTP_CKPT_CLOSE',
N'XE_BUFFERMGR_ALLPROCESSED_EVENT', N'XE_DISPATCHER_JOIN',
N'XE_DISPATCHER_WAIT', N'XE_LIVE_TARGET_TVF', N'XE_TIMER_EVENT',
N'SOS_WORK_DISPATCHER','RESERVED_MEMORY_ALLOCATION_EXT','SQLTRACE_WAIT_ENTRIES',
N'RBIO_COMM_RETRY')
AND [waiting_tasks_count] > 10
AND [wait_time_ms] > 100;
`
const sqlAzurePoolMemoryClerks = `
IF SERVERPROPERTY('EngineEdition') <> 5
OR NOT EXISTS (SELECT 1 FROM sys.database_service_objectives WHERE database_id = DB_ID() AND elastic_pool_name IS NOT NULL) BEGIN
DECLARE @ErrorMessage AS nvarchar(500) = 'Telegraf - Connection string Server:'+ @@SERVERNAME + ',Database:' + DB_NAME() +' is not an Azure SQL database in an elastic pool. Check the database_type parameter in the telegraf configuration.';
RAISERROR (@ErrorMessage,11,1)
RETURN
END
SELECT
'sqlserver_memory_clerks' AS [measurement]
,REPLACE(@@SERVERNAME, '\', ':') AS [sql_instance]
,(SELECT [elastic_pool_name] FROM sys.database_service_objectives WHERE database_id = DB_ID()) AS [elastic_pool_name]
,mc.[type] AS [clerk_type]
,SUM(mc.[pages_kb]) AS [size_kb]
FROM
sys.dm_os_memory_clerks AS mc
GROUP BY
mc.[type]
HAVING
SUM(mc.[pages_kb]) >= 1024
OPTION(RECOMPILE);
`
// Specific case on this query when cntr_type = 537003264 to return a percentage value between 0 and 100
// cf. https://docs.microsoft.com/en-us/sql/relational-databases/system-dynamic-management-views/sys-dm-os-performance-counters-transact-sql?view=azuresqldb-current
// Performance counters where the cntr_type column value is 537003264 display the ratio of a subset to its set as a percentage.
// For example, the Buffer Manager:Buffer cache hit ratio counter compares the total number of cache hits and the total number of cache lookups.
// As such, to get a snapshot-like reading of the last second only, you must compare the delta between the current value and the base value (denominator)
// between two collection points that are one second apart.
// The corresponding base value is the performance counter Buffer Manager:Buffer cache hit ratio base where the cntr_type column value is 1073939712.
const sqlAzurePoolPerformanceCounters = `
SET DEADLOCK_PRIORITY -10;
IF SERVERPROPERTY('EngineEdition') <> 5
OR NOT EXISTS (SELECT 1 FROM sys.database_service_objectives WHERE database_id = DB_ID() AND elastic_pool_name IS NOT NULL) BEGIN
DECLARE @ErrorMessage AS nvarchar(500) = 'Telegraf - Connection string Server:'+ @@SERVERNAME + ',Database:' + DB_NAME() +' is not an Azure SQL database in an elastic pool. Check the database_type parameter in the telegraf configuration.';
RAISERROR (@ErrorMessage,11,1)
RETURN
END
DECLARE @PCounters TABLE
(
[object_name] nvarchar(128),
[counter_name] nvarchar(128),
[instance_name] nvarchar(128),
[cntr_value] bigint,
[cntr_type] int
Primary Key([object_name],[counter_name],[instance_name])
);
WITH PerfCounters AS (
SELECT DISTINCT
RTRIM(pc.[object_name]) AS [object_name]
,RTRIM(pc.[counter_name]) AS [counter_name]
,ISNULL(gov.[database_name], RTRIM(pc.instance_name)) AS [instance_name]
,pc.[cntr_value] AS [cntr_value]
,pc.[cntr_type] AS [cntr_type]
FROM sys.dm_os_performance_counters AS pc
LEFT JOIN sys.dm_user_db_resource_governance AS gov
ON
TRY_CONVERT([uniqueidentifier], pc.[instance_name]) = gov.[physical_database_guid]
WHERE
/*filter out unnecessary SQL DB system database counters, other than master and tempdb*/
NOT (pc.[object_name] LIKE 'MSSQL%:Databases%' AND pc.[instance_name] IN ('model','model_masterdb','model_userdb','msdb','mssqlsystemresource'))
AND
(
pc.[counter_name] IN (
'SQL Compilations/sec'
,'SQL Re-Compilations/sec'
,'User Connections'
,'Batch Requests/sec'
,'Logouts/sec'
,'Logins/sec'
,'Processes blocked'
,'Latch Waits/sec'
,'Full Scans/sec'
,'Index Searches/sec'
,'Page Splits/sec'
,'Page lookups/sec'
,'Page reads/sec'
,'Page writes/sec'
,'Readahead pages/sec'
,'Lazy writes/sec'
,'Checkpoint pages/sec'
,'Table Lock Escalations/sec'
,'Page life expectancy'
,'Log File(s) Size (KB)'
,'Log File(s) Used Size (KB)'
,'Data File(s) Size (KB)'
,'Transactions/sec'
,'Write Transactions/sec'
,'Active Transactions'
,'Log Growths'
,'Active Temp Tables'
,'Logical Connections'
,'Temp Tables Creation Rate'
,'Temp Tables For Destruction'
,'Free Space in tempdb (KB)'
,'Version Store Size (KB)'
,'Memory Grants Pending'
,'Memory Grants Outstanding'
,'Free list stalls/sec'
,'Buffer cache hit ratio'
,'Buffer cache hit ratio base'
,'Backup/Restore Throughput/sec'
,'Total Server Memory (KB)'
,'Target Server Memory (KB)'
,'Log Flushes/sec'
,'Log Flush Wait Time'
,'Memory broker clerk size'
,'Log Bytes Flushed/sec'
,'Bytes Sent to Replica/sec'
,'Log Send Queue'
,'Bytes Sent to Transport/sec'
,'Sends to Replica/sec'
,'Bytes Sent to Transport/sec'
,'Sends to Transport/sec'
,'Bytes Received from Replica/sec'
,'Receives from Replica/sec'
,'Flow Control Time (ms/sec)'
,'Flow Control/sec'
,'Resent Messages/sec'
,'Redone Bytes/sec'
,'XTP Memory Used (KB)'
,'Transaction Delay'
,'Log Bytes Received/sec'
,'Log Apply Pending Queue'
,'Redone Bytes/sec'
,'Recovery Queue'
,'Log Apply Ready Queue'
,'CPU usage %'
,'CPU usage % base'
,'Queued requests'
,'Requests completed/sec'
,'Blocked tasks'
,'Active memory grant amount (KB)'
,'Disk Read Bytes/sec'
,'Disk Read IO Throttled/sec'
,'Disk Read IO/sec'
,'Disk Write Bytes/sec'
,'Disk Write IO Throttled/sec'
,'Disk Write IO/sec'
,'Used memory (KB)'
,'Forwarded Records/sec'
,'Background Writer pages/sec'
,'Percent Log Used'
,'Log Send Queue KB'
,'Redo Queue KB'
,'Mirrored Write Transactions/sec'
,'Group Commit Time'
,'Group Commits/Sec'
,'Workfiles Created/sec'
,'Worktables Created/sec'
,'Query Store CPU usage'
) OR (
pc.[object_name] LIKE '%User Settable%'
OR pc.[object_name] LIKE '%SQL Errors%'
OR pc.[object_name] LIKE '%Batch Resp Statistics%'
) OR (
pc.[instance_name] IN ('_Total')
AND pc.[counter_name] IN (
'Lock Timeouts/sec'
,'Lock Timeouts (timeout > 0)/sec'
,'Number of Deadlocks/sec'
,'Lock Waits/sec'
,'Latch Waits/sec'
)
)
)
)
INSERT INTO @PCounters select * from PerfCounters
SELECT
'sqlserver_performance' AS [measurement]
,REPLACE(@@SERVERNAME,'\',':') AS [sql_instance]
,pc.[object_name] AS [object]
,pc.[counter_name] AS [counter]
,CASE pc.[instance_name] WHEN '_Total' THEN 'Total' ELSE ISNULL(pc.[instance_name],'') END AS [instance]
,CAST(
CASE WHEN pc.[cntr_type] = 537003264 AND base.[cntr_value] > 0
THEN (pc.[cntr_value] * 1.0) / (base.[cntr_value] * 1.0) * 100
ELSE pc.[cntr_value]
END
AS float) AS [value]
,CAST(pc.[cntr_type] AS varchar(25)) AS [counter_type]
FROM @PCounters AS pc
LEFT OUTER JOIN @PCounters AS base
ON
pc.[counter_name] = REPLACE(base.[counter_name],' base','')
AND pc.[object_name] = base.[object_name]
AND pc.[instance_name] = base.[instance_name]
AND base.[cntr_type] = 1073939712
WHERE
pc.[cntr_type] <> 1073939712
OPTION(RECOMPILE)
`
const sqlAzurePoolSchedulers = `
IF SERVERPROPERTY('EngineEdition') <> 5
OR NOT EXISTS (SELECT 1 FROM sys.database_service_objectives WHERE database_id = DB_ID() AND elastic_pool_name IS NOT NULL) BEGIN
DECLARE @ErrorMessage AS nvarchar(500) = 'Telegraf - Connection string Server:'+ @@SERVERNAME + ',Database:' + DB_NAME() +' is not an Azure SQL database in an elastic pool. Check the database_type parameter in the telegraf configuration.';
RAISERROR (@ErrorMessage,11,1)
RETURN
END
SELECT
'sqlserver_schedulers' AS [measurement]
,REPLACE(@@SERVERNAME, '\', ':') AS [sql_instance]
,(SELECT [elastic_pool_name] FROM sys.database_service_objectives WHERE database_id = DB_ID()) AS [elastic_pool_name]
,[scheduler_id]
,[cpu_id]
,[status]
,[is_online]
,[is_idle]
,[preemptive_switches_count]
,[context_switches_count]
,[idle_switches_count]
,[current_tasks_count]
,[runnable_tasks_count]
,[current_workers_count]
,[active_workers_count]
,[work_queue_count]
,[pending_disk_io_count]
,[load_factor]
,[failed_to_create_worker]
,[quantum_length_us]
,[yield_count]
,[total_cpu_usage_ms]
,[total_cpu_idle_capped_ms]
,[total_scheduler_delay_ms]
,[ideal_workers_limit]
FROM
sys.dm_os_schedulers;
`

View file

@ -0,0 +1,322 @@
package sqlserver
import (
"os"
"testing"
"github.com/stretchr/testify/require"
"github.com/influxdata/telegraf/config"
"github.com/influxdata/telegraf/testutil"
)
func TestAzureSQLIntegration_ElasticPool_ResourceStats_Query(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
if os.Getenv("AZURESQL_POOL_CONNECTION_STRING") == "" {
t.Skip("Missing environment variable AZURESQL_POOL_CONNECTION_STRING")
}
connectionString := os.Getenv("AZURESQL_POOL_CONNECTION_STRING")
sl := config.NewSecret([]byte(connectionString))
server := &SQLServer{
Servers: []*config.Secret{&sl},
IncludeQuery: []string{"AzureSQLPoolResourceStats"},
AuthMethod: "connection_string",
DatabaseType: "AzureSQLPool",
}
var acc testutil.Accumulator
require.NoError(t, server.Start(&acc))
require.NoError(t, server.Gather(&acc))
require.True(t, acc.HasMeasurement("sqlserver_pool_resource_stats"))
require.True(t, acc.HasTag("sqlserver_pool_resource_stats", "sql_instance"))
require.True(t, acc.HasTag("sqlserver_pool_resource_stats", "elastic_pool_name"))
require.True(t, acc.HasField("sqlserver_pool_resource_stats", "snapshot_time"))
require.True(t, acc.HasFloatField("sqlserver_pool_resource_stats", "avg_cpu_percent"))
require.True(t, acc.HasFloatField("sqlserver_pool_resource_stats", "avg_data_io_percent"))
require.True(t, acc.HasFloatField("sqlserver_pool_resource_stats", "avg_log_write_percent"))
require.True(t, acc.HasFloatField("sqlserver_pool_resource_stats", "avg_storage_percent"))
require.True(t, acc.HasFloatField("sqlserver_pool_resource_stats", "max_worker_percent"))
require.True(t, acc.HasFloatField("sqlserver_pool_resource_stats", "max_session_percent"))
require.True(t, acc.HasInt64Field("sqlserver_pool_resource_stats", "storage_limit_mb"))
require.True(t, acc.HasFloatField("sqlserver_pool_resource_stats", "avg_instance_cpu_percent"))
require.True(t, acc.HasFloatField("sqlserver_pool_resource_stats", "avg_allocated_storage_percent"))
// This query should only return one row
require.Len(t, acc.Metrics, 1)
server.Stop()
}
func TestAzureSQLIntegration_ElasticPool_ResourceGovernance_Query(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
if os.Getenv("AZURESQL_POOL_CONNECTION_STRING") == "" {
t.Skip("Missing environment variable AZURESQL_POOL_CONNECTION_STRING")
}
connectionString := os.Getenv("AZURESQL_POOL_CONNECTION_STRING")
sl := config.NewSecret([]byte(connectionString))
server := &SQLServer{
Servers: []*config.Secret{&sl},
IncludeQuery: []string{"AzureSQLPoolResourceGovernance"},
AuthMethod: "connection_string",
DatabaseType: "AzureSQLPool",
}
var acc testutil.Accumulator
require.NoError(t, server.Start(&acc))
require.NoError(t, server.Gather(&acc))
require.True(t, acc.HasMeasurement("sqlserver_pool_resource_governance"))
require.True(t, acc.HasTag("sqlserver_pool_resource_governance", "sql_instance"))
require.True(t, acc.HasTag("sqlserver_pool_resource_governance", "elastic_pool_name"))
require.True(t, acc.HasTag("sqlserver_pool_resource_governance", "slo_name"))
require.True(t, acc.HasInt64Field("sqlserver_pool_resource_governance", "dtu_limit"))
require.True(t, acc.HasInt64Field("sqlserver_pool_resource_governance", "cpu_limit"))
require.True(t, acc.HasInt64Field("sqlserver_pool_resource_governance", "max_cpu"))
require.True(t, acc.HasInt64Field("sqlserver_pool_resource_governance", "cap_cpu"))
require.True(t, acc.HasInt64Field("sqlserver_pool_resource_governance", "max_db_memory"))
require.True(t, acc.HasInt64Field("sqlserver_pool_resource_governance", "max_db_max_size_in_mb"))
require.True(t, acc.HasInt64Field("sqlserver_pool_resource_governance", "db_file_growth_in_mb"))
require.True(t, acc.HasInt64Field("sqlserver_pool_resource_governance", "log_size_in_mb"))
require.True(t, acc.HasInt64Field("sqlserver_pool_resource_governance", "instance_cap_cpu"))
require.True(t, acc.HasInt64Field("sqlserver_pool_resource_governance", "instance_max_log_rate"))
require.True(t, acc.HasInt64Field("sqlserver_pool_resource_governance", "instance_max_worker_threads"))
require.True(t, acc.HasInt64Field("sqlserver_pool_resource_governance", "checkpoint_rate_mbps"))
require.True(t, acc.HasInt64Field("sqlserver_pool_resource_governance", "checkpoint_rate_io"))
require.True(t, acc.HasInt64Field("sqlserver_pool_resource_governance", "primary_group_max_workers"))
require.True(t, acc.HasInt64Field("sqlserver_pool_resource_governance", "primary_min_log_rate"))
require.True(t, acc.HasInt64Field("sqlserver_pool_resource_governance", "primary_max_log_rate"))
require.True(t, acc.HasInt64Field("sqlserver_pool_resource_governance", "primary_group_min_io"))
require.True(t, acc.HasInt64Field("sqlserver_pool_resource_governance", "primary_group_max_io"))
require.True(t, acc.HasFloatField("sqlserver_pool_resource_governance", "primary_group_min_cpu"))
require.True(t, acc.HasFloatField("sqlserver_pool_resource_governance", "primary_group_max_cpu"))
require.True(t, acc.HasInt64Field("sqlserver_pool_resource_governance", "primary_pool_max_workers"))
require.True(t, acc.HasInt64Field("sqlserver_pool_resource_governance", "pool_max_io"))
require.True(t, acc.HasInt64Field("sqlserver_pool_resource_governance", "volume_local_iops"))
require.True(t, acc.HasInt64Field("sqlserver_pool_resource_governance", "volume_managed_xstore_iops"))
require.True(t, acc.HasInt64Field("sqlserver_pool_resource_governance", "volume_external_xstore_iops"))
require.True(t, acc.HasInt64Field("sqlserver_pool_resource_governance", "volume_type_local_iops"))
require.True(t, acc.HasInt64Field("sqlserver_pool_resource_governance", "volume_type_managed_xstore_iops"))
require.True(t, acc.HasInt64Field("sqlserver_pool_resource_governance", "volume_type_external_xstore_iops"))
require.True(t, acc.HasInt64Field("sqlserver_pool_resource_governance", "volume_pfs_iops"))
require.True(t, acc.HasInt64Field("sqlserver_pool_resource_governance", "volume_type_pfs_iops"))
// This query should only return one row
require.Len(t, acc.Metrics, 1)
server.Stop()
}
func TestAzureSQLIntegration_ElasticPool_DatabaseIO_Query(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
if os.Getenv("AZURESQL_POOL_CONNECTION_STRING") == "" {
t.Skip("Missing environment variable AZURESQL_POOL_CONNECTION_STRING")
}
connectionString := os.Getenv("AZURESQL_POOL_CONNECTION_STRING")
sl := config.NewSecret([]byte(connectionString))
server := &SQLServer{
Servers: []*config.Secret{&sl},
IncludeQuery: []string{"AzureSQLPoolDatabaseIO"},
AuthMethod: "connection_string",
DatabaseType: "AzureSQLPool",
}
var acc testutil.Accumulator
require.NoError(t, server.Start(&acc))
require.NoError(t, server.Gather(&acc))
require.True(t, acc.HasMeasurement("sqlserver_database_io"))
require.True(t, acc.HasTag("sqlserver_database_io", "sql_instance"))
require.True(t, acc.HasTag("sqlserver_database_io", "elastic_pool_name"))
require.True(t, acc.HasTag("sqlserver_database_io", "database_name"))
require.True(t, acc.HasInt64Field("sqlserver_database_io", "database_id"))
require.True(t, acc.HasInt64Field("sqlserver_database_io", "file_id"))
require.True(t, acc.HasTag("sqlserver_database_io", "file_type"))
require.True(t, acc.HasInt64Field("sqlserver_database_io", "reads"))
require.True(t, acc.HasInt64Field("sqlserver_database_io", "read_bytes"))
require.True(t, acc.HasInt64Field("sqlserver_database_io", "read_latency_ms"))
require.True(t, acc.HasInt64Field("sqlserver_database_io", "write_latency_ms"))
require.True(t, acc.HasInt64Field("sqlserver_database_io", "writes"))
require.True(t, acc.HasInt64Field("sqlserver_database_io", "write_bytes"))
require.True(t, acc.HasInt64Field("sqlserver_database_io", "rg_read_stall_ms"))
require.True(t, acc.HasInt64Field("sqlserver_database_io", "rg_write_stall_ms"))
require.True(t, acc.HasInt64Field("sqlserver_database_io", "size_on_disk_bytes"))
require.True(t, acc.HasInt64Field("sqlserver_database_io", "size_on_disk_mb"))
server.Stop()
}
func TestAzureSQLIntegration_ElasticPool_OsWaitStats_Query(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
if os.Getenv("AZURESQL_POOL_CONNECTION_STRING") == "" {
t.Skip("Missing environment variable AZURESQL_POOL_CONNECTION_STRING")
}
connectionString := os.Getenv("AZURESQL_POOL_CONNECTION_STRING")
sl := config.NewSecret([]byte(connectionString))
server := &SQLServer{
Servers: []*config.Secret{&sl},
IncludeQuery: []string{"AzureSQLPoolOsWaitStats"},
AuthMethod: "connection_string",
DatabaseType: "AzureSQLPool",
}
var acc testutil.Accumulator
require.NoError(t, server.Start(&acc))
require.NoError(t, server.Gather(&acc))
require.True(t, acc.HasMeasurement("sqlserver_waitstats"))
require.True(t, acc.HasTag("sqlserver_waitstats", "sql_instance"))
require.True(t, acc.HasTag("sqlserver_waitstats", "elastic_pool_name"))
require.True(t, acc.HasTag("sqlserver_waitstats", "wait_type"))
require.True(t, acc.HasInt64Field("sqlserver_waitstats", "waiting_tasks_count"))
require.True(t, acc.HasInt64Field("sqlserver_waitstats", "wait_time_ms"))
require.True(t, acc.HasInt64Field("sqlserver_waitstats", "max_wait_time_ms"))
require.True(t, acc.HasInt64Field("sqlserver_waitstats", "signal_wait_time_ms"))
require.True(t, acc.HasInt64Field("sqlserver_waitstats", "resource_wait_ms"))
require.True(t, acc.HasTag("sqlserver_waitstats", "wait_category"))
server.Stop()
}
func TestAzureSQLIntegration_ElasticPool_MemoryClerks_Query(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
if os.Getenv("AZURESQL_POOL_CONNECTION_STRING") == "" {
t.Skip("Missing environment variable AZURESQL_POOL_CONNECTION_STRING")
}
connectionString := os.Getenv("AZURESQL_POOL_CONNECTION_STRING")
sl := config.NewSecret([]byte(connectionString))
server := &SQLServer{
Servers: []*config.Secret{&sl},
IncludeQuery: []string{"AzureSQLPoolMemoryClerks"},
AuthMethod: "connection_string",
DatabaseType: "AzureSQLPool",
}
var acc testutil.Accumulator
require.NoError(t, server.Start(&acc))
require.NoError(t, server.Gather(&acc))
require.True(t, acc.HasMeasurement("sqlserver_memory_clerks"))
require.True(t, acc.HasTag("sqlserver_memory_clerks", "sql_instance"))
require.True(t, acc.HasTag("sqlserver_memory_clerks", "elastic_pool_name"))
require.True(t, acc.HasTag("sqlserver_memory_clerks", "clerk_type"))
require.True(t, acc.HasInt64Field("sqlserver_memory_clerks", "size_kb"))
server.Stop()
}
func TestAzureSQLIntegration_ElasticPool_PerformanceCounters_Query(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
if os.Getenv("AZURESQL_POOL_CONNECTION_STRING") == "" {
t.Skip("Missing environment variable AZURESQL_POOL_CONNECTION_STRING")
}
connectionString := os.Getenv("AZURESQL_POOL_CONNECTION_STRING")
sl := config.NewSecret([]byte(connectionString))
server := &SQLServer{
Servers: []*config.Secret{&sl},
IncludeQuery: []string{"AzureSQLPoolPerformanceCounters"},
AuthMethod: "connection_string",
DatabaseType: "AzureSQLPool",
}
var acc testutil.Accumulator
require.NoError(t, server.Start(&acc))
require.NoError(t, server.Gather(&acc))
require.True(t, acc.HasMeasurement("sqlserver_performance"))
require.True(t, acc.HasTag("sqlserver_performance", "sql_instance"))
require.True(t, acc.HasTag("sqlserver_performance", "object"))
require.True(t, acc.HasTag("sqlserver_performance", "counter"))
require.True(t, acc.HasTag("sqlserver_performance", "instance"))
require.True(t, acc.HasFloatField("sqlserver_performance", "value"))
require.True(t, acc.HasTag("sqlserver_performance", "counter_type"))
server.Stop()
}
func TestAzureSQLIntegration_ElasticPool_Schedulers_Query(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
if os.Getenv("AZURESQL_POOL_CONNECTION_STRING") == "" {
t.Skip("Missing environment variable AZURESQL_POOL_CONNECTION_STRING")
}
connectionString := os.Getenv("AZURESQL_POOL_CONNECTION_STRING")
sl := config.NewSecret([]byte(connectionString))
server := &SQLServer{
Servers: []*config.Secret{&sl},
IncludeQuery: []string{"AzureSQLPoolSchedulers"},
AuthMethod: "connection_string",
DatabaseType: "AzureSQLPool",
}
var acc testutil.Accumulator
require.NoError(t, server.Start(&acc))
require.NoError(t, server.Gather(&acc))
require.True(t, acc.HasMeasurement("sqlserver_schedulers"))
require.True(t, acc.HasTag("sqlserver_schedulers", "sql_instance"))
require.True(t, acc.HasTag("sqlserver_schedulers", "elastic_pool_name"))
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "scheduler_id"))
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "cpu_id"))
require.True(t, acc.HasTag("sqlserver_schedulers", "status"))
require.True(t, acc.HasField("sqlserver_schedulers", "is_online"))
require.True(t, acc.HasField("sqlserver_schedulers", "is_idle"))
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "preemptive_switches_count"))
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "context_switches_count"))
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "idle_switches_count"))
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "current_tasks_count"))
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "runnable_tasks_count"))
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "current_workers_count"))
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "active_workers_count"))
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "work_queue_count"))
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "pending_disk_io_count"))
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "load_factor"))
require.True(t, acc.HasField("sqlserver_schedulers", "failed_to_create_worker"))
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "quantum_length_us"))
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "yield_count"))
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "total_cpu_usage_ms"))
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "total_cpu_idle_capped_ms"))
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "total_scheduler_delay_ms"))
require.True(t, acc.HasInt64Field("sqlserver_schedulers", "ideal_workers_limit"))
server.Stop()
}

View file

@ -0,0 +1,17 @@
package sqlserver
import "time"
// New token structure for Azure Identity SDK
type azureToken struct {
token string
expiresOn time.Time
}
// IsExpired helper method for Azure token expiry
func (t *azureToken) IsExpired() bool {
if t == nil {
return true
}
return time.Now().After(t.expiresOn)
}

View file

@ -0,0 +1,98 @@
package sqlserver
import (
"net/url"
"strings"
)
const (
emptySQLInstance = "<empty-sql-instance>"
emptyDatabaseName = "<empty-database-name>"
)
// getConnectionIdentifiers returns the sqlInstance and databaseName from the given connection string.
// The name of the SQL instance is returned as-is in the connection string
// If the connection string could not be parsed or sqlInstance/databaseName were not present, a placeholder value is returned
func getConnectionIdentifiers(connectionString string) (sqlInstance, databaseName string) {
if len(connectionString) == 0 {
return emptySQLInstance, emptyDatabaseName
}
trimmedConnectionString := strings.TrimSpace(connectionString)
if strings.HasPrefix(trimmedConnectionString, "odbc:") {
connectionStringWithoutOdbc := strings.TrimPrefix(trimmedConnectionString, "odbc:")
return parseConnectionStringKeyValue(connectionStringWithoutOdbc)
}
if strings.HasPrefix(trimmedConnectionString, "sqlserver://") {
return parseConnectionStringURL(trimmedConnectionString)
}
return parseConnectionStringKeyValue(trimmedConnectionString)
}
// parseConnectionStringKeyValue parses a "key=value;" connection string and returns the SQL instance and database name
func parseConnectionStringKeyValue(connectionString string) (sqlInstance, databaseName string) {
sqlInstance = ""
databaseName = ""
keyValuePairs := strings.Split(connectionString, ";")
for _, keyValuePair := range keyValuePairs {
if len(keyValuePair) == 0 {
continue
}
keyAndValue := strings.SplitN(keyValuePair, "=", 2)
key := strings.TrimSpace(strings.ToLower(keyAndValue[0]))
if len(key) == 0 {
continue
}
value := ""
if len(keyAndValue) > 1 {
value = strings.TrimSpace(keyAndValue[1])
}
if strings.EqualFold("server", key) {
sqlInstance = value
continue
}
if strings.EqualFold("database", key) {
databaseName = value
}
}
if sqlInstance == "" {
sqlInstance = emptySQLInstance
}
if databaseName == "" {
databaseName = emptyDatabaseName
}
return sqlInstance, databaseName
}
// parseConnectionStringURL parses a URL-formatted connection string and returns the SQL instance and database name
func parseConnectionStringURL(connectionString string) (sqlInstance, databaseName string) {
databaseName = emptyDatabaseName
u, err := url.Parse(connectionString)
if err != nil {
return emptySQLInstance, emptyDatabaseName
}
sqlInstance = u.Hostname()
if len(u.Path) > 1 {
// There was a SQL instance name specified in addition to the host
// E.g. "the.host.com:1234/InstanceName" or "the.host.com/InstanceName"
sqlInstance = sqlInstance + "\\" + u.Path[1:]
}
query := u.Query()
for key, value := range query {
if strings.EqualFold("database", key) {
databaseName = value[0]
break
}
}
return sqlInstance, databaseName
}

View file

@ -0,0 +1,131 @@
# Read metrics from Microsoft SQL Server
[[inputs.sqlserver]]
## Specify instances to monitor with a list of connection strings.
## All connection parameters are optional.
## By default, the host is localhost, listening on default port, TCP 1433.
## for Windows, the user is the currently running AD user (SSO).
## See https://github.com/microsoft/go-mssqldb for detailed connection
## parameters, in particular, tls connections can be created like so:
## "encrypt=true;certificate=<cert>;hostNameInCertificate=<SqlServer host fqdn>"
servers = [
"Server=192.168.1.10;Port=1433;User Id=<user>;Password=<pw>;app name=telegraf;log=1;",
]
## Timeout for query execution operation
## Note that the timeout for queries is per query not per gather.
## 0 value means no timeout
# query_timeout = "0s"
## Authentication method
## valid methods: "connection_string", "AAD"
# auth_method = "connection_string"
## ClientID is the is the client ID of the user assigned identity of the VM
## that should be used to authenticate to the Azure SQL server.
# client_id = ""
## "database_type" enables a specific set of queries depending on the database type. If specified, it replaces azuredb = true/false and query_version = 2
## In the config file, the sql server plugin section should be repeated each with a set of servers for a specific database_type.
## Possible values for database_type are - "SQLServer" or "AzureSQLDB" or "AzureSQLManagedInstance" or "AzureSQLPool"
database_type = "SQLServer"
## A list of queries to include. If not specified, all the below listed queries are used.
include_query = []
## A list of queries to explicitly ignore.
exclude_query = ["SQLServerAvailabilityReplicaStates", "SQLServerDatabaseReplicaStates"]
## Force using the deprecated ADAL authentication method instead of the recommended
## MSAL method. Setting this option is not recommended and only exists for backward
## compatibility.
# use_deprecated_adal_authentication = false
## Queries enabled by default for database_type = "SQLServer" are -
## SQLServerPerformanceCounters, SQLServerWaitStatsCategorized, SQLServerDatabaseIO, SQLServerProperties, SQLServerMemoryClerks,
## SQLServerSchedulers, SQLServerRequests, SQLServerVolumeSpace, SQLServerCpu, SQLServerAvailabilityReplicaStates, SQLServerDatabaseReplicaStates,
## SQLServerRecentBackups
## Queries enabled by default for database_type = "AzureSQLDB" are -
## AzureSQLDBResourceStats, AzureSQLDBResourceGovernance, AzureSQLDBWaitStats, AzureSQLDBDatabaseIO, AzureSQLDBServerProperties,
## AzureSQLDBOsWaitstats, AzureSQLDBMemoryClerks, AzureSQLDBPerformanceCounters, AzureSQLDBRequests, AzureSQLDBSchedulers
## Queries enabled by default for database_type = "AzureSQLManagedInstance" are -
## AzureSQLMIResourceStats, AzureSQLMIResourceGovernance, AzureSQLMIDatabaseIO, AzureSQLMIServerProperties, AzureSQLMIOsWaitstats,
## AzureSQLMIMemoryClerks, AzureSQLMIPerformanceCounters, AzureSQLMIRequests, AzureSQLMISchedulers
## Queries enabled by default for database_type = "AzureSQLPool" are -
## AzureSQLPoolResourceStats, AzureSQLPoolResourceGovernance, AzureSQLPoolDatabaseIO, AzureSQLPoolWaitStats,
## AzureSQLPoolMemoryClerks, AzureSQLPoolPerformanceCounters, AzureSQLPoolSchedulers
## Queries enabled by default for database_type = "AzureArcSQLManagedInstance" are -
## AzureSQLMIDatabaseIO, AzureSQLMIServerProperties, AzureSQLMIOsWaitstats,
## AzureSQLMIMemoryClerks, AzureSQLMIPerformanceCounters, AzureSQLMIRequests, AzureSQLMISchedulers
## Following are old config settings
## You may use them only if you are using the earlier flavor of queries, however it is recommended to use
## the new mechanism of identifying the database_type there by use it's corresponding queries
## Optional parameter, setting this to 2 will use a new version
## of the collection queries that break compatibility with the original
## dashboards.
## Version 2 - is compatible from SQL Server 2012 and later versions and also for SQL Azure DB
# query_version = 2
## If you are using AzureDB, setting this to true will gather resource utilization metrics
# azuredb = false
## Toggling this to true will emit an additional metric called "sqlserver_telegraf_health".
## This metric tracks the count of attempted queries and successful queries for each SQL instance specified in "servers".
## The purpose of this metric is to assist with identifying and diagnosing any connectivity or query issues.
## This setting/metric is optional and is disabled by default.
# health_metric = false
## Possible queries across different versions of the collectors
## Queries enabled by default for specific Database Type
## database_type = AzureSQLDB by default collects the following queries
## - AzureSQLDBWaitStats
## - AzureSQLDBResourceStats
## - AzureSQLDBResourceGovernance
## - AzureSQLDBDatabaseIO
## - AzureSQLDBServerProperties
## - AzureSQLDBOsWaitstats
## - AzureSQLDBMemoryClerks
## - AzureSQLDBPerformanceCounters
## - AzureSQLDBRequests
## - AzureSQLDBSchedulers
## database_type = AzureSQLManagedInstance by default collects the following queries
## - AzureSQLMIResourceStats
## - AzureSQLMIResourceGovernance
## - AzureSQLMIDatabaseIO
## - AzureSQLMIServerProperties
## - AzureSQLMIOsWaitstats
## - AzureSQLMIMemoryClerks
## - AzureSQLMIPerformanceCounters
## - AzureSQLMIRequests
## - AzureSQLMISchedulers
## database_type = AzureSQLPool by default collects the following queries
## - AzureSQLPoolResourceStats
## - AzureSQLPoolResourceGovernance
## - AzureSQLPoolDatabaseIO
## - AzureSQLPoolOsWaitStats,
## - AzureSQLPoolMemoryClerks
## - AzureSQLPoolPerformanceCounters
## - AzureSQLPoolSchedulers
## database_type = SQLServer by default collects the following queries
## - SQLServerPerformanceCounters
## - SQLServerWaitStatsCategorized
## - SQLServerDatabaseIO
## - SQLServerProperties
## - SQLServerMemoryClerks
## - SQLServerSchedulers
## - SQLServerRequests
## - SQLServerVolumeSpace
## - SQLServerCpu
## - SQLServerRecentBackups
## and following as optional (if mentioned in the include_query list)
## - SQLServerAvailabilityReplicaStates
## - SQLServerDatabaseReplicaStates

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,720 @@
//go:generate ../../../tools/readme_config_includer/generator
package sqlserver
import (
"context"
"database/sql"
_ "embed"
"errors"
"fmt"
"strings"
"sync"
"time"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
// Legacy ADAL package - kept for backward compatibility
"github.com/Azure/go-autorest/autorest/adal"
mssql "github.com/microsoft/go-mssqldb"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/config"
"github.com/influxdata/telegraf/filter"
"github.com/influxdata/telegraf/plugins/inputs"
)
//go:embed sample.conf
var sampleConfig string
const (
defaultServer = "Server=.;app name=telegraf;log=1;"
typeAzureSQLDB = "AzureSQLDB"
typeAzureSQLManagedInstance = "AzureSQLManagedInstance"
typeAzureSQLPool = "AzureSQLPool"
typeSQLServer = "SQLServer"
typeAzureArcSQLManagedInstance = "AzureArcSQLManagedInstance"
healthMetricName = "sqlserver_telegraf_health"
healthMetricInstanceTag = "sql_instance"
healthMetricDatabaseTag = "database_name"
healthMetricAttemptedQueries = "attempted_queries"
healthMetricSuccessfulQueries = "successful_queries"
healthMetricDatabaseType = "database_type"
sqlAzureResourceID = "https://database.windows.net/"
)
type SQLServer struct {
Servers []*config.Secret `toml:"servers"`
QueryTimeout config.Duration `toml:"query_timeout"`
AuthMethod string `toml:"auth_method"`
ClientID string `toml:"client_id"`
QueryVersion int `toml:"query_version" deprecated:"1.16.0;1.35.0;use 'database_type' instead"`
AzureDB bool `toml:"azuredb" deprecated:"1.16.0;1.35.0;use 'database_type' instead"`
DatabaseType string `toml:"database_type"`
IncludeQuery []string `toml:"include_query"`
ExcludeQuery []string `toml:"exclude_query"`
HealthMetric bool `toml:"health_metric"`
Log telegraf.Logger `toml:"-"`
pools []*sql.DB
queries mapQuery
// Legacy token - kept for backward compatibility
adalToken *adal.Token
// New token using Azure Identity SDK
azToken *azureToken
// Config option to use legacy ADAL authentication instead of the newer Azure Identity SDK
// When true, the deprecated ADAL library will be used
// When false (default), the new Azure Identity SDK will be used
UseAdalToken bool `toml:"use_deprecated_adal_authentication" deprecated:"1.40.0;migrate to MSAL authentication"`
muCacheLock sync.RWMutex
}
type query struct {
ScriptName string
Script string
ResultByRow bool
OrderedColumns []string
}
type mapQuery map[string]query
// healthMetric struct tracking the number of attempted vs. successful connections for each connection string
type healthMetric struct {
attemptedQueries int
successfulQueries int
}
type scanner interface {
Scan(dest ...interface{}) error
}
func (*SQLServer) SampleConfig() string {
return sampleConfig
}
func (s *SQLServer) Init() error {
if len(s.Servers) == 0 {
srv := config.NewSecret([]byte(defaultServer))
s.Servers = append(s.Servers, &srv)
}
return nil
}
// Start initialize a list of connection pools
func (s *SQLServer) Start(acc telegraf.Accumulator) error {
if err := s.initQueries(); err != nil {
acc.AddError(err)
return err
}
// initialize mutual exclusion lock
s.muCacheLock = sync.RWMutex{}
for _, serv := range s.Servers {
var pool *sql.DB
switch strings.ToLower(s.AuthMethod) {
case "connection_string":
// Get the connection string potentially containing secrets
dsn, err := serv.Get()
if err != nil {
acc.AddError(err)
continue
}
// Use the DSN (connection string) directly. In this case,
// empty username/password causes use of Windows
// integrated authentication.
pool, err = sql.Open("mssql", dsn.String())
dsn.Destroy()
if err != nil {
acc.AddError(err)
continue
}
case "aad":
// AAD Auth with system-assigned managed identity (MSI)
// AAD Auth is only supported for Azure SQL Database or Azure SQL Managed Instance
if s.DatabaseType == "SQLServer" {
err := errors.New("database connection failed : AAD auth is not supported for SQL VM i.e. DatabaseType=SQLServer")
acc.AddError(err)
continue
}
// get token from in-memory cache variable or from Azure Active Directory
tokenProvider, err := s.getTokenProvider()
if err != nil {
acc.AddError(fmt.Errorf("error creating AAD token provider for system assigned Azure managed identity: %w", err))
continue
}
// Get the connection string potentially containing secrets
dsn, err := serv.Get()
if err != nil {
acc.AddError(err)
continue
}
connector, err := mssql.NewAccessTokenConnector(dsn.String(), tokenProvider)
dsn.Destroy()
if err != nil {
acc.AddError(fmt.Errorf("error creating the SQL connector: %w", err))
continue
}
pool = sql.OpenDB(connector)
default:
return fmt.Errorf("unknown auth method: %v", s.AuthMethod)
}
s.pools = append(s.pools, pool)
}
return nil
}
func (s *SQLServer) Gather(acc telegraf.Accumulator) error {
var wg sync.WaitGroup
var mutex sync.Mutex
var healthMetrics = make(map[string]*healthMetric)
for i, pool := range s.pools {
dnsSecret, err := s.Servers[i].Get()
if err != nil {
acc.AddError(err)
continue
}
dsn := dnsSecret.String()
dnsSecret.Destroy()
for _, q := range s.queries {
wg.Add(1)
go func(pool *sql.DB, q query, dsn string) {
defer wg.Done()
queryError := s.gatherServer(pool, q, acc, dsn)
if s.HealthMetric {
mutex.Lock()
gatherHealth(healthMetrics, dsn, queryError)
mutex.Unlock()
}
acc.AddError(queryError)
}(pool, q, dsn)
}
}
wg.Wait()
if s.HealthMetric {
s.accHealth(healthMetrics, acc)
}
return nil
}
// Stop cleanup server connection pools
func (s *SQLServer) Stop() {
for _, pool := range s.pools {
_ = pool.Close()
}
}
func (s *SQLServer) initQueries() error {
s.queries = make(mapQuery)
queries := s.queries
s.Log.Infof("Config: database_type: %s , query_version:%d , azuredb: %t", s.DatabaseType, s.QueryVersion, s.AzureDB)
// To prevent query definition conflicts
// Constant definitions for type "AzureSQLDB" start with sqlAzureDB
// Constant definitions for type "AzureSQLManagedInstance" start with sqlAzureMI
// Constant definitions for type "AzureSQLPool" start with sqlAzurePool
// Constant definitions for type "AzureArcSQLManagedInstance" start with sqlAzureArcMI
// Constant definitions for type "SQLServer" start with sqlServer
if s.DatabaseType == typeAzureSQLDB {
queries["AzureSQLDBResourceStats"] = query{ScriptName: "AzureSQLDBResourceStats", Script: sqlAzureDBResourceStats, ResultByRow: false}
queries["AzureSQLDBResourceGovernance"] = query{ScriptName: "AzureSQLDBResourceGovernance", Script: sqlAzureDBResourceGovernance, ResultByRow: false}
queries["AzureSQLDBWaitStats"] = query{ScriptName: "AzureSQLDBWaitStats", Script: sqlAzureDBWaitStats, ResultByRow: false}
queries["AzureSQLDBDatabaseIO"] = query{ScriptName: "AzureSQLDBDatabaseIO", Script: sqlAzureDBDatabaseIO, ResultByRow: false}
queries["AzureSQLDBServerProperties"] = query{ScriptName: "AzureSQLDBServerProperties", Script: sqlAzureDBProperties, ResultByRow: false}
queries["AzureSQLDBOsWaitstats"] = query{ScriptName: "AzureSQLOsWaitstats", Script: sqlAzureDBOsWaitStats, ResultByRow: false}
queries["AzureSQLDBMemoryClerks"] = query{ScriptName: "AzureSQLDBMemoryClerks", Script: sqlAzureDBMemoryClerks, ResultByRow: false}
queries["AzureSQLDBPerformanceCounters"] = query{ScriptName: "AzureSQLDBPerformanceCounters", Script: sqlAzureDBPerformanceCounters, ResultByRow: false}
queries["AzureSQLDBRequests"] = query{ScriptName: "AzureSQLDBRequests", Script: sqlAzureDBRequests, ResultByRow: false}
queries["AzureSQLDBSchedulers"] = query{ScriptName: "AzureSQLDBSchedulers", Script: sqlAzureDBSchedulers, ResultByRow: false}
} else if s.DatabaseType == typeAzureSQLManagedInstance {
queries["AzureSQLMIResourceStats"] = query{ScriptName: "AzureSQLMIResourceStats", Script: sqlAzureMIResourceStats, ResultByRow: false}
queries["AzureSQLMIResourceGovernance"] = query{ScriptName: "AzureSQLMIResourceGovernance", Script: sqlAzureMIResourceGovernance, ResultByRow: false}
queries["AzureSQLMIDatabaseIO"] = query{ScriptName: "AzureSQLMIDatabaseIO", Script: sqlAzureMIDatabaseIO, ResultByRow: false}
queries["AzureSQLMIServerProperties"] = query{ScriptName: "AzureSQLMIServerProperties", Script: sqlAzureMIProperties, ResultByRow: false}
queries["AzureSQLMIOsWaitstats"] = query{ScriptName: "AzureSQLMIOsWaitstats", Script: sqlAzureMIOsWaitStats, ResultByRow: false}
queries["AzureSQLMIMemoryClerks"] = query{ScriptName: "AzureSQLMIMemoryClerks", Script: sqlAzureMIMemoryClerks, ResultByRow: false}
queries["AzureSQLMIPerformanceCounters"] = query{ScriptName: "AzureSQLMIPerformanceCounters", Script: sqlAzureMIPerformanceCounters, ResultByRow: false}
queries["AzureSQLMIRequests"] = query{ScriptName: "AzureSQLMIRequests", Script: sqlAzureMIRequests, ResultByRow: false}
queries["AzureSQLMISchedulers"] = query{ScriptName: "AzureSQLMISchedulers", Script: sqlAzureMISchedulers, ResultByRow: false}
} else if s.DatabaseType == typeAzureSQLPool {
queries["AzureSQLPoolResourceStats"] = query{ScriptName: "AzureSQLPoolResourceStats", Script: sqlAzurePoolResourceStats, ResultByRow: false}
queries["AzureSQLPoolResourceGovernance"] =
query{ScriptName: "AzureSQLPoolResourceGovernance", Script: sqlAzurePoolResourceGovernance, ResultByRow: false}
queries["AzureSQLPoolDatabaseIO"] = query{ScriptName: "AzureSQLPoolDatabaseIO", Script: sqlAzurePoolDatabaseIO, ResultByRow: false}
queries["AzureSQLPoolOsWaitStats"] = query{ScriptName: "AzureSQLPoolOsWaitStats", Script: sqlAzurePoolOsWaitStats, ResultByRow: false}
queries["AzureSQLPoolMemoryClerks"] = query{ScriptName: "AzureSQLPoolMemoryClerks", Script: sqlAzurePoolMemoryClerks, ResultByRow: false}
queries["AzureSQLPoolPerformanceCounters"] =
query{ScriptName: "AzureSQLPoolPerformanceCounters", Script: sqlAzurePoolPerformanceCounters, ResultByRow: false}
queries["AzureSQLPoolSchedulers"] = query{ScriptName: "AzureSQLPoolSchedulers", Script: sqlAzurePoolSchedulers, ResultByRow: false}
} else if s.DatabaseType == typeAzureArcSQLManagedInstance {
queries["AzureArcSQLMIDatabaseIO"] = query{ScriptName: "AzureArcSQLMIDatabaseIO", Script: sqlAzureArcMIDatabaseIO, ResultByRow: false}
queries["AzureArcSQLMIServerProperties"] = query{ScriptName: "AzureArcSQLMIServerProperties", Script: sqlAzureArcMIProperties, ResultByRow: false}
queries["AzureArcSQLMIOsWaitstats"] = query{ScriptName: "AzureArcSQLMIOsWaitstats", Script: sqlAzureArcMIOsWaitStats, ResultByRow: false}
queries["AzureArcSQLMIMemoryClerks"] = query{ScriptName: "AzureArcSQLMIMemoryClerks", Script: sqlAzureArcMIMemoryClerks, ResultByRow: false}
queries["AzureArcSQLMIPerformanceCounters"] =
query{ScriptName: "AzureArcSQLMIPerformanceCounters", Script: sqlAzureArcMIPerformanceCounters, ResultByRow: false}
queries["AzureArcSQLMIRequests"] = query{ScriptName: "AzureArcSQLMIRequests", Script: sqlAzureArcMIRequests, ResultByRow: false}
queries["AzureArcSQLMISchedulers"] = query{ScriptName: "AzureArcSQLMISchedulers", Script: sqlAzureArcMISchedulers, ResultByRow: false}
} else if s.DatabaseType == typeSQLServer { // These are still V2 queries and have not been refactored yet.
queries["SQLServerPerformanceCounters"] = query{ScriptName: "SQLServerPerformanceCounters", Script: sqlServerPerformanceCounters, ResultByRow: false}
queries["SQLServerWaitStatsCategorized"] = query{ScriptName: "SQLServerWaitStatsCategorized", Script: sqlServerWaitStatsCategorized, ResultByRow: false}
queries["SQLServerDatabaseIO"] = query{ScriptName: "SQLServerDatabaseIO", Script: sqlServerDatabaseIO, ResultByRow: false}
queries["SQLServerProperties"] = query{ScriptName: "SQLServerProperties", Script: sqlServerProperties, ResultByRow: false}
queries["SQLServerMemoryClerks"] = query{ScriptName: "SQLServerMemoryClerks", Script: sqlServerMemoryClerks, ResultByRow: false}
queries["SQLServerSchedulers"] = query{ScriptName: "SQLServerSchedulers", Script: sqlServerSchedulers, ResultByRow: false}
queries["SQLServerRequests"] = query{ScriptName: "SQLServerRequests", Script: sqlServerRequests, ResultByRow: false}
queries["SQLServerVolumeSpace"] = query{ScriptName: "SQLServerVolumeSpace", Script: sqlServerVolumeSpace, ResultByRow: false}
queries["SQLServerCpu"] = query{ScriptName: "SQLServerCpu", Script: sqlServerRingBufferCPU, ResultByRow: false}
queries["SQLServerAvailabilityReplicaStates"] =
query{ScriptName: "SQLServerAvailabilityReplicaStates", Script: sqlServerAvailabilityReplicaStates, ResultByRow: false}
queries["SQLServerDatabaseReplicaStates"] =
query{ScriptName: "SQLServerDatabaseReplicaStates", Script: sqlServerDatabaseReplicaStates, ResultByRow: false}
queries["SQLServerRecentBackups"] = query{ScriptName: "SQLServerRecentBackups", Script: sqlServerRecentBackups, ResultByRow: false}
queries["SQLServerPersistentVersionStore"] =
query{ScriptName: "SQLServerPersistentVersionStore", Script: sqlServerPersistentVersionStore, ResultByRow: false}
} else {
// If this is an AzureDB instance, grab some extra metrics
if s.AzureDB {
queries["AzureDBResourceStats"] = query{ScriptName: "AzureDBPerformanceCounters", Script: sqlAzureDBResourceStats, ResultByRow: false}
queries["AzureDBResourceGovernance"] = query{ScriptName: "AzureDBPerformanceCounters", Script: sqlAzureDBResourceGovernance, ResultByRow: false}
}
// Decide if we want to run version 1 or version 2 queries
if s.QueryVersion == 2 {
queries["PerformanceCounters"] = query{ScriptName: "PerformanceCounters", Script: sqlPerformanceCountersV2, ResultByRow: true}
queries["WaitStatsCategorized"] = query{ScriptName: "WaitStatsCategorized", Script: sqlWaitStatsCategorizedV2, ResultByRow: false}
queries["DatabaseIO"] = query{ScriptName: "DatabaseIO", Script: sqlDatabaseIOV2, ResultByRow: false}
queries["ServerProperties"] = query{ScriptName: "ServerProperties", Script: sqlServerPropertiesV2, ResultByRow: false}
queries["MemoryClerk"] = query{ScriptName: "MemoryClerk", Script: sqlMemoryClerkV2, ResultByRow: false}
queries["Schedulers"] = query{ScriptName: "Schedulers", Script: sqlServerSchedulersV2, ResultByRow: false}
queries["SqlRequests"] = query{ScriptName: "SqlRequests", Script: sqlServerRequestsV2, ResultByRow: false}
queries["VolumeSpace"] = query{ScriptName: "VolumeSpace", Script: sqlServerVolumeSpaceV2, ResultByRow: false}
queries["Cpu"] = query{ScriptName: "Cpu", Script: sqlServerCPUV2, ResultByRow: false}
} else {
queries["PerformanceCounters"] = query{ScriptName: "PerformanceCounters", Script: sqlPerformanceCounters, ResultByRow: true}
queries["WaitStatsCategorized"] = query{ScriptName: "WaitStatsCategorized", Script: sqlWaitStatsCategorized, ResultByRow: false}
queries["CPUHistory"] = query{ScriptName: "CPUHistory", Script: sqlCPUHistory, ResultByRow: false}
queries["DatabaseIO"] = query{ScriptName: "DatabaseIO", Script: sqlDatabaseIO, ResultByRow: false}
queries["DatabaseSize"] = query{ScriptName: "DatabaseSize", Script: sqlDatabaseSize, ResultByRow: false}
queries["DatabaseStats"] = query{ScriptName: "DatabaseStats", Script: sqlDatabaseStats, ResultByRow: false}
queries["DatabaseProperties"] = query{ScriptName: "DatabaseProperties", Script: sqlDatabaseProperties, ResultByRow: false}
queries["MemoryClerk"] = query{ScriptName: "MemoryClerk", Script: sqlMemoryClerk, ResultByRow: false}
queries["VolumeSpace"] = query{ScriptName: "VolumeSpace", Script: sqlVolumeSpace, ResultByRow: false}
queries["PerformanceMetrics"] = query{ScriptName: "PerformanceMetrics", Script: sqlPerformanceMetrics, ResultByRow: false}
}
}
filterQueries, err := filter.NewIncludeExcludeFilter(s.IncludeQuery, s.ExcludeQuery)
if err != nil {
return err
}
for query := range queries {
if !filterQueries.Match(query) {
delete(queries, query)
}
}
queryList := make([]string, 0, len(queries))
for query := range queries {
queryList = append(queryList, query)
}
s.Log.Infof("Config: Effective Queries: %#v\n", queryList)
return nil
}
func (s *SQLServer) gatherServer(pool *sql.DB, query query, acc telegraf.Accumulator, connectionString string) error {
// execute query
ctx := context.Background()
// Use the query timeout if any
if s.QueryTimeout > 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, time.Duration(s.QueryTimeout))
defer cancel()
}
rows, err := pool.QueryContext(ctx, query.Script)
if err != nil {
serverName, databaseName := getConnectionIdentifiers(connectionString)
// Error msg based on the format in SSMS. SQLErrorClass() is another term for severity/level: http://msdn.microsoft.com/en-us/library/dd304156.aspx
var sqlErr mssql.Error
if errors.As(err, &sqlErr) {
return fmt.Errorf("query %s failed for server: %s and database: %s with Msg %d, Level %d, State %d:, Line %d, Error: %w", query.ScriptName,
serverName, databaseName, sqlErr.SQLErrorNumber(), sqlErr.SQLErrorClass(), sqlErr.SQLErrorState(), sqlErr.SQLErrorLineNo(), err)
}
return fmt.Errorf("query %s failed for server: %s and database: %s with Error: %w", query.ScriptName, serverName, databaseName, err)
}
defer rows.Close()
// grab the column information from the result
query.OrderedColumns, err = rows.Columns()
if err != nil {
return err
}
for rows.Next() {
err = s.accRow(query, acc, rows)
if err != nil {
return err
}
}
return rows.Err()
}
func (s *SQLServer) accRow(query query, acc telegraf.Accumulator, row scanner) error {
var fields = make(map[string]interface{})
// store the column name with its *interface{}
columnMap := make(map[string]*interface{})
for _, column := range query.OrderedColumns {
columnMap[column] = new(interface{})
}
columnVars := make([]interface{}, 0, len(columnMap))
// populate the array of interface{} with the pointers in the right order
for i := 0; i < len(columnMap); i++ {
columnVars = append(columnVars, columnMap[query.OrderedColumns[i]])
}
// deconstruct array of variables and send to Scan
err := row.Scan(columnVars...)
if err != nil {
return err
}
// measurement: identified by the header
// tags: all other fields of type string
tags := make(map[string]string, len(columnMap)+1)
var measurement string
for header, val := range columnMap {
if str, ok := (*val).(string); ok {
if header == "measurement" {
measurement = str
} else {
tags[header] = str
}
}
}
if s.DatabaseType != "" {
tags["measurement_db_type"] = s.DatabaseType
}
if query.ResultByRow {
// add measurement to Accumulator
acc.AddFields(measurement,
map[string]interface{}{"value": *columnMap["value"]},
tags, time.Now())
} else {
// values
for header, val := range columnMap {
if _, ok := (*val).(string); !ok {
fields[header] = *val
}
}
// add fields to Accumulator
acc.AddFields(measurement, fields, tags, time.Now())
}
return nil
}
// gatherHealth stores info about any query errors in the healthMetrics map
func gatherHealth(healthMetrics map[string]*healthMetric, serv string, queryError error) {
if healthMetrics[serv] == nil {
healthMetrics[serv] = &healthMetric{}
}
healthMetrics[serv].attemptedQueries++
if queryError == nil {
healthMetrics[serv].successfulQueries++
}
}
// accHealth accumulates the query health data contained within the healthMetrics map
func (s *SQLServer) accHealth(healthMetrics map[string]*healthMetric, acc telegraf.Accumulator) {
for connectionString, connectionStats := range healthMetrics {
sqlInstance, databaseName := getConnectionIdentifiers(connectionString)
tags := map[string]string{healthMetricInstanceTag: sqlInstance, healthMetricDatabaseTag: databaseName}
fields := map[string]interface{}{
healthMetricAttemptedQueries: connectionStats.attemptedQueries,
healthMetricSuccessfulQueries: connectionStats.successfulQueries,
healthMetricDatabaseType: s.getDatabaseTypeToLog(),
}
acc.AddFields(healthMetricName, fields, tags, time.Now())
}
}
// getDatabaseTypeToLog returns the type of database monitored by this plugin instance
func (s *SQLServer) getDatabaseTypeToLog() string {
if s.DatabaseType == typeAzureSQLDB || s.DatabaseType == typeAzureSQLManagedInstance || s.DatabaseType == typeSQLServer {
return s.DatabaseType
}
logname := fmt.Sprintf("QueryVersion-%d", s.QueryVersion)
if s.AzureDB {
logname += "-AzureDB"
}
return logname
}
// ------------------------------------------------------------------------------
// Token Provider Implementation
// ------------------------------------------------------------------------------
// getTokenProvider returns a function that provides authentication tokens for SQL Server.
//
// DEPRECATION NOTICE:
// The ADAL authentication library is deprecated and will be removed in a future version.
// It is strongly recommended to migrate to the Azure Identity SDK.
// See the migration documentation at: https://learn.microsoft.com/en-us/azure/active-directory/develop/msal-migration
//
// This implementation supports both authentication methods:
// 1. Azure Identity SDK (default, recommended)
// 2. Legacy ADAL library (deprecated, maintained for backward compatibility)
//
// To control which authentication library is used, set the use_deprecated_adal_authentication config option:
// - use_deprecated_adal_authentication = true : Use legacy ADAL authentication (deprecated)
// - use_deprecated_adal_authentication = false : Use Azure Identity SDK (recommended)
// - Not set : Use Azure Identity SDK (recommended)
func (s *SQLServer) getTokenProvider() (func() (string, error), error) {
// Check if use_deprecated_adal_authentication config option is set to determine which auth method to use
// Default to using Azure Identity SDK if the config is not set
useAzureIdentity := !s.UseAdalToken
if useAzureIdentity {
s.Log.Debugf("Using Azure Identity SDK for authentication (recommended)")
} else {
s.Log.Debugf("Using legacy ADAL for authentication (deprecated, will be removed in 1.40.0)")
}
var tokenString string
if useAzureIdentity {
// Use Azure Identity SDK
s.muCacheLock.RLock()
token, err := s.loadAzureToken()
s.muCacheLock.RUnlock()
// If the token is nil, expired, or there was an error loading it, refresh the token
if err != nil || token == nil || token.IsExpired() {
// Refresh token within a write-lock
s.muCacheLock.Lock()
defer s.muCacheLock.Unlock()
// Load token again, in case it's been refreshed by another thread
token, err = s.loadAzureToken()
// Check loaded token's error/validity, then refresh/save token
if err != nil || token == nil || token.IsExpired() {
// Get new token
newToken, err := s.refreshAzureToken()
if err != nil {
return nil, err
}
// Use the refreshed token
tokenString = newToken.token
} else {
// Use locally cached token
tokenString = token.token
}
} else {
// Use locally cached token
tokenString = token.token
}
} else {
// Use legacy ADAL approach for backward compatibility
s.muCacheLock.RLock()
token, err := s.loadToken()
s.muCacheLock.RUnlock()
// If there's an error while loading token or found an expired token, refresh token and save it
if err != nil || token.IsExpired() {
// Refresh token within a write-lock
s.muCacheLock.Lock()
defer s.muCacheLock.Unlock()
// Load token again, in case it's been refreshed by another thread
token, err = s.loadToken()
// Check loaded token's error/validity, then refresh/save token
if err != nil || token.IsExpired() {
// Get new token
spt, err := s.refreshToken()
if err != nil {
return nil, err
}
// Use the refreshed token
tokenString = spt.OAuthToken()
} else {
// Use locally cached token
tokenString = token.OAuthToken()
}
} else {
// Use locally cached token
tokenString = token.OAuthToken()
}
}
// Return acquired token
//nolint:unparam // token provider function always returns nil error in this scenario
return func() (string, error) {
return tokenString, nil
}, nil
}
// ------------------------------------------------------------------------------
// Legacy ADAL Token Methods - Kept for backward compatibility
// ------------------------------------------------------------------------------
// loadToken loads a token from in-memory cache using the legacy ADAL method.
//
// Deprecated: This method uses the deprecated ADAL library and will be removed in a future version.
// Use the Azure Identity SDK instead of setting use_deprecated_adal_authentication = false or omitting it.
// See migration documentation: https://learn.microsoft.com/en-us/azure/active-directory/develop/msal-migration
func (s *SQLServer) loadToken() (*adal.Token, error) {
// This method currently does a simplistic task of reading from a variable (in-mem cache);
// however, it's been structured here to allow extending the cache mechanism to a different approach in future
if s.adalToken == nil {
return nil, errors.New("token is nil or failed to load existing token")
}
return s.adalToken, nil
}
// refreshToken refreshes the token using the legacy ADAL method.
//
// Deprecated: This method uses the deprecated ADAL library and will be removed in a future version.
// Use the Azure Identity SDK instead of setting use_deprecated_adal_authentication = false or omitting it.
// See migration documentation: https://learn.microsoft.com/en-us/azure/active-directory/develop/msal-migration
func (s *SQLServer) refreshToken() (*adal.Token, error) {
// get MSI endpoint to get a token
msiEndpoint, err := adal.GetMSIVMEndpoint()
if err != nil {
return nil, fmt.Errorf("failed to get MSI endpoint: %w", err)
}
// get a new token for the resource id
var spt *adal.ServicePrincipalToken
if s.ClientID == "" {
// Using system-assigned managed identity
s.Log.Debugf("Using system-assigned managed identity with ADAL")
spt, err = adal.NewServicePrincipalTokenFromMSI(msiEndpoint, sqlAzureResourceID)
if err != nil {
return nil, fmt.Errorf("failed to create service principal token from MSI: %w", err)
}
} else {
// Using user-assigned managed identity
s.Log.Debugf("Using user-assigned managed identity with ClientID: %s with ADAL", s.ClientID)
spt, err = adal.NewServicePrincipalTokenFromMSIWithUserAssignedID(msiEndpoint, sqlAzureResourceID, s.ClientID)
if err != nil {
return nil, fmt.Errorf("failed to create service principal token from MSI with user-assigned ID: %w", err)
}
}
// ensure the token is fresh
if err := spt.EnsureFresh(); err != nil {
return nil, fmt.Errorf("failed to ensure token freshness: %w", err)
}
// save token to local in-mem cache
s.adalToken = &adal.Token{
AccessToken: spt.Token().AccessToken,
RefreshToken: spt.Token().RefreshToken,
ExpiresIn: spt.Token().ExpiresIn,
ExpiresOn: spt.Token().ExpiresOn,
NotBefore: spt.Token().NotBefore,
Resource: spt.Token().Resource,
Type: spt.Token().Type,
}
return s.adalToken, nil
}
// ------------------------------------------------------------------------------
// New Azure Identity SDK Token Methods
// ------------------------------------------------------------------------------
// loadAzureToken loads a token from in-memory cache using the Azure Identity SDK.
//
// This is the recommended authentication method for Azure SQL resources.
func (s *SQLServer) loadAzureToken() (*azureToken, error) {
// This method reads from variable (in-mem cache) but can be extended
// for different cache mechanisms in the future
if s.azToken == nil {
return nil, errors.New("token is nil or failed to load existing token")
}
return s.azToken, nil
}
// refreshAzureToken refreshes the token using the Azure Identity SDK.
//
// This is the recommended authentication method for Azure SQL resources.
func (s *SQLServer) refreshAzureToken() (*azureToken, error) {
var options *azidentity.ManagedIdentityCredentialOptions
if s.ClientID != "" {
options = &azidentity.ManagedIdentityCredentialOptions{
ID: azidentity.ResourceID(s.ClientID),
}
}
cred, err := azidentity.NewManagedIdentityCredential(options)
if err != nil {
return nil, fmt.Errorf("failed to create managed identity credential: %w", err)
}
// Get token from Azure AD
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
accessToken, err := cred.GetToken(ctx, policy.TokenRequestOptions{
Scopes: []string{sqlAzureResourceID + "/.default"},
})
if err != nil {
credType := "system-assigned"
if s.ClientID != "" {
credType = fmt.Sprintf("user-assigned (ClientID: %s)", s.ClientID)
}
return nil, fmt.Errorf("failed to get token using %s managed identity: %w", credType, err)
}
// Save token to cache
s.azToken = &azureToken{
token: accessToken.Token,
expiresOn: accessToken.ExpiresOn,
}
return s.azToken, nil
}
func init() {
inputs.Add("sqlserver", func() telegraf.Input {
return &SQLServer{
AuthMethod: "connection_string",
}
})
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff