Merge branch 'master' of github.com:influxdata/telegraf
This commit is contained in:
commit
94d441c488
|
|
@ -71,14 +71,6 @@ commands:
|
|||
paths:
|
||||
- 'dist'
|
||||
jobs:
|
||||
linter:
|
||||
executor: go-1_16
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
key: go-mod-v1-{{ checksum "go.sum" }}
|
||||
- run: wget -O- -nv https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.37.0
|
||||
- run: make lint
|
||||
deps:
|
||||
executor: go-1_16
|
||||
steps:
|
||||
|
|
@ -209,7 +201,6 @@ workflows:
|
|||
version: 2
|
||||
check:
|
||||
jobs:
|
||||
- 'linter'
|
||||
- 'macdeps':
|
||||
filters:
|
||||
tags:
|
||||
|
|
@ -287,7 +278,6 @@ workflows:
|
|||
only: /.*/
|
||||
nightly:
|
||||
jobs:
|
||||
- 'linter'
|
||||
- 'deps'
|
||||
- 'macdeps'
|
||||
- 'test-go-1_15':
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
name: golangci-lint
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
schedule:
|
||||
# Trigger every day at 16:00 UTC
|
||||
- cron: '0 16 * * *'
|
||||
jobs:
|
||||
golangci-pr:
|
||||
name: lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v2
|
||||
with:
|
||||
version: v1.38
|
||||
only-new-issues: true
|
||||
args: --timeout=5m0s
|
||||
|
|
@ -5859,6 +5859,11 @@
|
|||
# ## 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
|
||||
|
||||
# # Gather timeseries from Google Cloud Platform v3 monitoring API
|
||||
# [[inputs.stackdriver]]
|
||||
|
|
|
|||
|
|
@ -101,6 +101,12 @@ GO
|
|||
## 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 accross different versions of the collectors
|
||||
## Queries enabled by default for specific Database Type
|
||||
|
||||
|
|
@ -323,4 +329,20 @@ 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
|
||||
|
|
|
|||
|
|
@ -438,106 +438,111 @@ WITH PerfCounters AS (
|
|||
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'
|
||||
,'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'
|
||||
/*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'
|
||||
,'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'
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,100 @@
|
|||
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 string, 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 string, 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 string, databaseName string) {
|
||||
sqlInstance = emptySqlInstance
|
||||
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
|
||||
}
|
||||
|
|
@ -21,6 +21,7 @@ type SQLServer struct {
|
|||
DatabaseType string `toml:"database_type"`
|
||||
IncludeQuery []string `toml:"include_query"`
|
||||
ExcludeQuery []string `toml:"exclude_query"`
|
||||
HealthMetric bool `toml:"health_metric"`
|
||||
queries MapQuery
|
||||
isInitialized bool
|
||||
}
|
||||
|
|
@ -36,8 +37,29 @@ type Query struct {
|
|||
// MapQuery type
|
||||
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
|
||||
}
|
||||
|
||||
const defaultServer = "Server=.;app name=telegraf;log=1;"
|
||||
|
||||
const (
|
||||
typeAzureSQLDB = "AzureSQLDB"
|
||||
typeAzureSQLManagedInstance = "AzureSQLManagedInstance"
|
||||
typeSQLServer = "SQLServer"
|
||||
)
|
||||
|
||||
const (
|
||||
healthMetricName = "sqlserver_telegraf_health"
|
||||
healthMetricInstanceTag = "sql_instance"
|
||||
healthMetricDatabaseTag = "database_name"
|
||||
healthMetricAttemptedQueries = "attempted_queries"
|
||||
healthMetricSuccessfulQueries = "successful_queries"
|
||||
healthMetricDatabaseType = "database_type"
|
||||
)
|
||||
|
||||
const sampleConfig = `
|
||||
## Specify instances to monitor with a list of connection strings.
|
||||
## All connection parameters are optional.
|
||||
|
|
@ -124,7 +146,7 @@ func initQueries(s *SQLServer) error {
|
|||
// Constant defintiions for type "AzureSQLDB" start with sqlAzureDB
|
||||
// Constant defintiions for type "AzureSQLManagedInstance" start with sqlAzureMI
|
||||
// Constant defintiions for type "SQLServer" start with sqlServer
|
||||
if s.DatabaseType == "AzureSQLDB" {
|
||||
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}
|
||||
|
|
@ -135,7 +157,7 @@ func initQueries(s *SQLServer) error {
|
|||
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 == "AzureSQLManagedInstance" {
|
||||
} 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}
|
||||
|
|
@ -145,7 +167,7 @@ func initQueries(s *SQLServer) error {
|
|||
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 == "SQLServer" { //These are still V2 queries and have not been refactored yet.
|
||||
} 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}
|
||||
|
|
@ -222,18 +244,33 @@ func (s *SQLServer) Gather(acc telegraf.Accumulator) error {
|
|||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
var mutex sync.Mutex
|
||||
var healthMetrics = make(map[string]*HealthMetric)
|
||||
|
||||
for _, serv := range s.Servers {
|
||||
for _, query := range s.queries {
|
||||
wg.Add(1)
|
||||
go func(serv string, query Query) {
|
||||
defer wg.Done()
|
||||
acc.AddError(s.gatherServer(serv, query, acc))
|
||||
queryError := s.gatherServer(serv, query, acc)
|
||||
|
||||
if s.HealthMetric {
|
||||
mutex.Lock()
|
||||
s.gatherHealth(healthMetrics, serv, queryError)
|
||||
mutex.Unlock()
|
||||
}
|
||||
|
||||
acc.AddError(queryError)
|
||||
}(serv, query)
|
||||
}
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
if s.HealthMetric {
|
||||
s.accHealth(healthMetrics, acc)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -323,6 +360,46 @@ func (s *SQLServer) accRow(query Query, acc telegraf.Accumulator, row scanner) e
|
|||
return nil
|
||||
}
|
||||
|
||||
// gatherHealth stores info about any query errors in the healthMetrics map
|
||||
func (s *SQLServer) 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
|
||||
}
|
||||
|
||||
func (s *SQLServer) Init() error {
|
||||
if len(s.Servers) == 0 {
|
||||
log.Println("W! Warning: Server list is empty.")
|
||||
|
|
|
|||
|
|
@ -138,6 +138,7 @@ func TestSqlServer_MultipleInstanceIntegration(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
assert.Equal(t, s.isInitialized, true)
|
||||
assert.Equal(t, s2.isInitialized, true)
|
||||
|
||||
// acc includes size metrics, and excludes memory metrics
|
||||
assert.False(t, acc.HasMeasurement("Memory breakdown (%)"))
|
||||
assert.True(t, acc.HasMeasurement("Log size (bytes)"))
|
||||
|
|
@ -147,6 +148,89 @@ func TestSqlServer_MultipleInstanceIntegration(t *testing.T) {
|
|||
assert.False(t, acc2.HasMeasurement("Log size (bytes)"))
|
||||
}
|
||||
|
||||
func TestSqlServer_MultipleInstanceWithHealthMetricIntegration(t *testing.T) {
|
||||
// Invoke Gather() from two separate configurations and
|
||||
// confirm they don't interfere with each other.
|
||||
// This test is intentionally similar to TestSqlServer_MultipleInstanceIntegration.
|
||||
// It is separated to ensure that the health metric code does not affect other metrics
|
||||
t.Skip("Skipping as unable to open tcp connection with host '127.0.0.1:1433")
|
||||
|
||||
testServer := "Server=127.0.0.1;Port=1433;User Id=SA;Password=ABCabc01;app name=telegraf;log=1"
|
||||
s := &SQLServer{
|
||||
Servers: []string{testServer},
|
||||
ExcludeQuery: []string{"MemoryClerk"},
|
||||
}
|
||||
s2 := &SQLServer{
|
||||
Servers: []string{testServer},
|
||||
ExcludeQuery: []string{"DatabaseSize"},
|
||||
HealthMetric: true,
|
||||
}
|
||||
|
||||
var acc, acc2 testutil.Accumulator
|
||||
err := s.Gather(&acc)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, s.isInitialized, true)
|
||||
assert.Equal(t, s2.isInitialized, false)
|
||||
|
||||
err = s2.Gather(&acc2)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, s.isInitialized, true)
|
||||
assert.Equal(t, s2.isInitialized, true)
|
||||
|
||||
// acc includes size metrics, and excludes memory metrics and the health metric
|
||||
assert.False(t, acc.HasMeasurement(healthMetricName))
|
||||
assert.False(t, acc.HasMeasurement("Memory breakdown (%)"))
|
||||
assert.True(t, acc.HasMeasurement("Log size (bytes)"))
|
||||
|
||||
// acc2 includes memory metrics and the health metric, and excludes size metrics
|
||||
assert.True(t, acc2.HasMeasurement(healthMetricName))
|
||||
assert.True(t, acc2.HasMeasurement("Memory breakdown (%)"))
|
||||
assert.False(t, acc2.HasMeasurement("Log size (bytes)"))
|
||||
|
||||
sqlInstance, database := getConnectionIdentifiers(testServer)
|
||||
tags := map[string]string{healthMetricInstanceTag: sqlInstance, healthMetricDatabaseTag: database}
|
||||
assert.True(t, acc2.HasPoint(healthMetricName, tags, healthMetricAttemptedQueries, 9))
|
||||
assert.True(t, acc2.HasPoint(healthMetricName, tags, healthMetricSuccessfulQueries, 9))
|
||||
}
|
||||
|
||||
func TestSqlServer_HealthMetric(t *testing.T) {
|
||||
fakeServer1 := "localhost\\fakeinstance1;Database=fakedb1"
|
||||
fakeServer2 := "localhost\\fakeinstance2;Database=fakedb2"
|
||||
|
||||
s1 := &SQLServer{
|
||||
Servers: []string{fakeServer1, fakeServer2},
|
||||
IncludeQuery: []string{"DatabaseSize", "MemoryClerk"},
|
||||
HealthMetric: true,
|
||||
}
|
||||
|
||||
s2 := &SQLServer{
|
||||
Servers: []string{fakeServer1},
|
||||
IncludeQuery: []string{"DatabaseSize"},
|
||||
}
|
||||
|
||||
// acc1 should have the health metric because it is specified in the config
|
||||
var acc1 testutil.Accumulator
|
||||
s1.Gather(&acc1)
|
||||
assert.True(t, acc1.HasMeasurement(healthMetricName))
|
||||
|
||||
// There will be 2 attempted queries (because we specified 2 queries in IncludeQuery)
|
||||
// Both queries should fail because the specified SQL instances do not exist
|
||||
sqlInstance1, database1 := getConnectionIdentifiers(fakeServer1)
|
||||
tags1 := map[string]string{healthMetricInstanceTag: sqlInstance1, healthMetricDatabaseTag: database1}
|
||||
assert.True(t, acc1.HasPoint(healthMetricName, tags1, healthMetricAttemptedQueries, 2))
|
||||
assert.True(t, acc1.HasPoint(healthMetricName, tags1, healthMetricSuccessfulQueries, 0))
|
||||
|
||||
sqlInstance2, database2 := getConnectionIdentifiers(fakeServer2)
|
||||
tags2 := map[string]string{healthMetricInstanceTag: sqlInstance2, healthMetricDatabaseTag: database2}
|
||||
assert.True(t, acc1.HasPoint(healthMetricName, tags2, healthMetricAttemptedQueries, 2))
|
||||
assert.True(t, acc1.HasPoint(healthMetricName, tags2, healthMetricSuccessfulQueries, 0))
|
||||
|
||||
// acc2 should not have the health metric because it is not specified in the config
|
||||
var acc2 testutil.Accumulator
|
||||
s2.Gather(&acc2)
|
||||
assert.False(t, acc2.HasMeasurement(healthMetricName))
|
||||
}
|
||||
|
||||
func TestSqlServer_MultipleInit(t *testing.T) {
|
||||
|
||||
s := &SQLServer{}
|
||||
|
|
@ -169,6 +253,86 @@ func TestSqlServer_MultipleInit(t *testing.T) {
|
|||
assert.Equal(t, s2.isInitialized, true)
|
||||
}
|
||||
|
||||
func TestSqlServer_ConnectionString(t *testing.T) {
|
||||
// URL format
|
||||
connectionString := "sqlserver://username:password@hostname.database.windows.net?database=databasename&connection+timeout=30"
|
||||
sqlInstance, database := getConnectionIdentifiers(connectionString)
|
||||
assert.Equal(t, "hostname.database.windows.net", sqlInstance)
|
||||
assert.Equal(t, "databasename", database)
|
||||
|
||||
connectionString = " sqlserver://hostname2.somethingelse.net:1433?database=databasename2"
|
||||
sqlInstance, database = getConnectionIdentifiers(connectionString)
|
||||
assert.Equal(t, "hostname2.somethingelse.net", sqlInstance)
|
||||
assert.Equal(t, "databasename2", database)
|
||||
|
||||
connectionString = "sqlserver://hostname3:1433/SqlInstanceName3?database=databasename3"
|
||||
sqlInstance, database = getConnectionIdentifiers(connectionString)
|
||||
assert.Equal(t, "hostname3\\SqlInstanceName3", sqlInstance)
|
||||
assert.Equal(t, "databasename3", database)
|
||||
|
||||
connectionString = " sqlserver://hostname4/SqlInstanceName4?database=databasename4&connection%20timeout=30"
|
||||
sqlInstance, database = getConnectionIdentifiers(connectionString)
|
||||
assert.Equal(t, "hostname4\\SqlInstanceName4", sqlInstance)
|
||||
assert.Equal(t, "databasename4", database)
|
||||
|
||||
connectionString = " sqlserver://username:password@hostname5?connection%20timeout=30"
|
||||
sqlInstance, database = getConnectionIdentifiers(connectionString)
|
||||
assert.Equal(t, "hostname5", sqlInstance)
|
||||
assert.Equal(t, emptyDatabaseName, database)
|
||||
|
||||
// odbc format
|
||||
connectionString = "odbc:server=hostname.database.windows.net;user id=sa;database=master;Trusted_Connection=Yes;Integrated Security=true;"
|
||||
sqlInstance, database = getConnectionIdentifiers(connectionString)
|
||||
assert.Equal(t, "hostname.database.windows.net", sqlInstance)
|
||||
assert.Equal(t, "master", database)
|
||||
|
||||
connectionString = " odbc:server=192.168.0.1;user id=somethingelse;Integrated Security=true;Database=mydb "
|
||||
sqlInstance, database = getConnectionIdentifiers(connectionString)
|
||||
assert.Equal(t, "192.168.0.1", sqlInstance)
|
||||
assert.Equal(t, "mydb", database)
|
||||
|
||||
connectionString = " odbc:Server=servername\\instancename;Database=dbname;"
|
||||
sqlInstance, database = getConnectionIdentifiers(connectionString)
|
||||
assert.Equal(t, "servername\\instancename", sqlInstance)
|
||||
assert.Equal(t, "dbname", database)
|
||||
|
||||
connectionString = "server=hostname2.database.windows.net;user id=sa;Trusted_Connection=Yes;Integrated Security=true;"
|
||||
sqlInstance, database = getConnectionIdentifiers(connectionString)
|
||||
assert.Equal(t, "hostname2.database.windows.net", sqlInstance)
|
||||
assert.Equal(t, emptyDatabaseName, database)
|
||||
|
||||
connectionString = "invalid connection string"
|
||||
sqlInstance, database = getConnectionIdentifiers(connectionString)
|
||||
assert.Equal(t, emptySqlInstance, sqlInstance)
|
||||
assert.Equal(t, emptyDatabaseName, database)
|
||||
|
||||
// Key/value format
|
||||
connectionString = " server=hostname.database.windows.net;user id=sa;database=master;Trusted_Connection=Yes;Integrated Security=true"
|
||||
sqlInstance, database = getConnectionIdentifiers(connectionString)
|
||||
assert.Equal(t, "hostname.database.windows.net", sqlInstance)
|
||||
assert.Equal(t, "master", database)
|
||||
|
||||
connectionString = " server=192.168.0.1;user id=somethingelse;Integrated Security=true;Database=mydb;"
|
||||
sqlInstance, database = getConnectionIdentifiers(connectionString)
|
||||
assert.Equal(t, "192.168.0.1", sqlInstance)
|
||||
assert.Equal(t, "mydb", database)
|
||||
|
||||
connectionString = "Server=servername\\instancename;Database=dbname; "
|
||||
sqlInstance, database = getConnectionIdentifiers(connectionString)
|
||||
assert.Equal(t, "servername\\instancename", sqlInstance)
|
||||
assert.Equal(t, "dbname", database)
|
||||
|
||||
connectionString = "server=hostname2.database.windows.net;user id=sa;Trusted_Connection=Yes;Integrated Security=true "
|
||||
sqlInstance, database = getConnectionIdentifiers(connectionString)
|
||||
assert.Equal(t, "hostname2.database.windows.net", sqlInstance)
|
||||
assert.Equal(t, emptyDatabaseName, database)
|
||||
|
||||
connectionString = "invalid connection string"
|
||||
sqlInstance, database = getConnectionIdentifiers(connectionString)
|
||||
assert.Equal(t, emptySqlInstance, sqlInstance)
|
||||
assert.Equal(t, emptyDatabaseName, database)
|
||||
}
|
||||
|
||||
func TestSqlServer_AGQueriesApplicableForDatabaseTypeSQLServer(t *testing.T) {
|
||||
// This test case checks where Availability Group (AG / HADR) queries return an output when included for processing for DatabaseType = SQLServer
|
||||
// And they should not be processed when DatabaseType = AzureSQLDB
|
||||
|
|
|
|||
|
|
@ -178,4 +178,5 @@ aws s3 sync ./ "s3://$BUCKET/" \
|
|||
--include "*.zip" \
|
||||
--include "*.DIGESTS" \
|
||||
--include "*.asc" \
|
||||
--include "*.dmg" \
|
||||
--acl public-read
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ else
|
|||
cd $currentDir
|
||||
osascript<<EOF
|
||||
tell application "Terminal"
|
||||
do script "$currentDir/../Resources/usr/bin/telegraf $@"
|
||||
do script " $currentDir/../Resources/usr/bin/telegraf $@"
|
||||
end tell
|
||||
EOF
|
||||
fi
|
||||
Loading…
Reference in New Issue