Merge branch 'master' of github.com:influxdata/telegraf

This commit is contained in:
helenosheaa 2021-03-11 16:21:03 -05:00
commit 94d441c488
10 changed files with 501 additions and 114 deletions

View File

@ -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':

23
.github/workflows/golangci-lint.yml vendored Normal file
View File

@ -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

View File

@ -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]]

View File

@ -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

View File

@ -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'
)
)
)
)

View File

@ -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
}

View File

@ -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.")

View File

@ -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

View File

@ -178,4 +178,5 @@ aws s3 sync ./ "s3://$BUCKET/" \
--include "*.zip" \
--include "*.DIGESTS" \
--include "*.asc" \
--include "*.dmg" \
--acl public-read

View File

@ -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