From 1007aef4c8bab34599e0e2b11cefb70b4f6bd559 Mon Sep 17 00:00:00 2001 From: Mario Castro Date: Thu, 10 Jan 2019 14:59:04 +0100 Subject: [PATCH] Performance Metricset on X-Pack MSSQL Metricbeat module (#9826) Performance Metricset, as it name implies, adds key performance information about a MS SQL instance (no database specific performance info in this Metricset). User connections, page splits, compilations or batch requests are added. --- CHANGELOG.next.asciidoc | 1 + x-pack/metricbeat/include/list.go | 1 + x-pack/metricbeat/metricbeat.reference.yml | 1 + .../metricbeat/module/mssql/_meta/config.yml | 1 + x-pack/metricbeat/module/mssql/connection.go | 27 ++++ x-pack/metricbeat/module/mssql/fields.go | 2 +- .../module/mssql/performance/_meta/data.json | 45 +++++++ .../mssql/performance/_meta/docs.asciidoc | 12 ++ .../module/mssql/performance/_meta/fields.yml | 63 +++++++++ .../module/mssql/performance/data.go | 37 ++++++ .../performance/data_integration_test.go | 63 +++++++++ .../module/mssql/performance/performance.go | 122 ++++++++++++++++++ .../performance_integration_test.go | 98 ++++++++++++++ .../metricbeat/modules.d/mssql.yml.disabled | 1 + x-pack/metricbeat/tests/system/test_mssql.py | 28 ++++ 15 files changed, 501 insertions(+), 1 deletion(-) create mode 100644 x-pack/metricbeat/module/mssql/connection.go create mode 100644 x-pack/metricbeat/module/mssql/performance/_meta/data.json create mode 100644 x-pack/metricbeat/module/mssql/performance/_meta/docs.asciidoc create mode 100644 x-pack/metricbeat/module/mssql/performance/_meta/fields.yml create mode 100644 x-pack/metricbeat/module/mssql/performance/data.go create mode 100644 x-pack/metricbeat/module/mssql/performance/data_integration_test.go create mode 100644 x-pack/metricbeat/module/mssql/performance/performance.go create mode 100644 x-pack/metricbeat/module/mssql/performance/performance_integration_test.go diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index 02a0d58a781..06f6ed40205 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -111,6 +111,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Add `key` metricset to the Redis module. {issue}9582[9582] {pull}9657[9657] {pull}9746[9746] - Add `socket_summary` metricset to system defaults, removing experimental tag and supporting Windows {pull}9709[9709] - Add docker `event` metricset. {pull}9856[9856] +- Add 'performance' metricset to x-pack mssql module {pull}9826[9826] *Packetbeat* diff --git a/x-pack/metricbeat/include/list.go b/x-pack/metricbeat/include/list.go index be12c4e52d8..5bf352e8b3e 100644 --- a/x-pack/metricbeat/include/list.go +++ b/x-pack/metricbeat/include/list.go @@ -10,4 +10,5 @@ import ( // Import packages that need to register themselves. _ "github.com/elastic/beats/x-pack/metricbeat/module/mssql" _ "github.com/elastic/beats/x-pack/metricbeat/module/mssql/db" + _ "github.com/elastic/beats/x-pack/metricbeat/module/mssql/performance" ) diff --git a/x-pack/metricbeat/metricbeat.reference.yml b/x-pack/metricbeat/metricbeat.reference.yml index 771a259f70e..db6d6640e2d 100644 --- a/x-pack/metricbeat/metricbeat.reference.yml +++ b/x-pack/metricbeat/metricbeat.reference.yml @@ -491,6 +491,7 @@ metricbeat.modules: - module: mssql metricsets: - "db" + - "performance" hosts: ["sqlserver://sa@localhost"] period: 10s diff --git a/x-pack/metricbeat/module/mssql/_meta/config.yml b/x-pack/metricbeat/module/mssql/_meta/config.yml index 4008489e10d..038955a319c 100644 --- a/x-pack/metricbeat/module/mssql/_meta/config.yml +++ b/x-pack/metricbeat/module/mssql/_meta/config.yml @@ -1,6 +1,7 @@ - module: mssql metricsets: - "db" + - "performance" hosts: ["sqlserver://sa@localhost"] period: 10s diff --git a/x-pack/metricbeat/module/mssql/connection.go b/x-pack/metricbeat/module/mssql/connection.go new file mode 100644 index 00000000000..3528e2371be --- /dev/null +++ b/x-pack/metricbeat/module/mssql/connection.go @@ -0,0 +1,27 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package mssql + +import ( + "database/sql" + + "github.com/pkg/errors" +) + +// NewConnection returns a connection already established with MSSQL +func NewConnection(uri string) (*sql.DB, error) { + db, err := sql.Open("sqlserver", uri) + if err != nil { + return nil, errors.Wrap(err, "could not create db instance") + } + + // Check the connection before executing all queries to reduce the number + // of connection errors that we might encounter. + if err = db.Ping(); err != nil { + err = errors.Wrap(err, "error doing ping to db") + } + + return db, err +} diff --git a/x-pack/metricbeat/module/mssql/fields.go b/x-pack/metricbeat/module/mssql/fields.go index 390f0abf4b8..61a95fddce2 100644 --- a/x-pack/metricbeat/module/mssql/fields.go +++ b/x-pack/metricbeat/module/mssql/fields.go @@ -18,5 +18,5 @@ func init() { // Asset returns asset data func Asset() string { - return "eJyslEGPmzAQhe/8iqe97x/gUKnSXiq1hyq9R4MZghtjs55ho/TXVzghCwGiZJXcMn5+/ubNiFfs+ZijEXl3GaBWHed4Sf9fMqBkMdG2aoPP8WuDze+faELZOc6AyI5JOEfBShlQWXal5BkAvMJTw5/O/U+PLefYxdC158rE/tu5OLYZW5XFpbTkNfMrCzSs0RphxcE6h4rV1LC+CrGhXgQqQqdgMnUvr2JoQJB3B+H4wXFkfU01ISOlgoQnh2uMM863820cao4MrXngRk0fjILZn9C5TIhXZktkYzpbzo6uCP7UfOkBP94W5KdW9nw8hFguZuDCbistGd52QrsvRrHpDZAMJmOqQky5aCQvZFLNhd2DQWhQcqvNLcHNk+otIJ+YCxfWMMYoxVFZFhULoxH7jxGqlMC862kXLvi5YHi1E17ahUf6rxnBmK61XN7B9fQo1t6G9TeNboQzZmmN3kPyHS1Hw17p7wVilYwENOiHetrEdNqrbzJXLtAcagAW6w1vHYluCzL7+wZITehOLMMiJ2xvTh+f3i2xrTreXpinD/0x3gdW4X8AAAD//45S2Xs=" + return "eJzMl8GO4zYMhu95CmLvsw+QQ4F29rLAFph2ejdoiY7ZyJJHlJOmT19QTiaOY6fJNEEnt0gU9fEnIdJPsKbdEhqRN7cASJwcLeFL/v9lAWBJTOQ2cfBL+PUVXn/7AU2wnaMFQCRHKLSEkhIuAComZ2W5AAB4Ao8NHT3rL+1aWsIqhq7dr5y4/2m/OHQzdGXL96UpX2f+bAkNpchGKMGWnYOKkqmBfRVig2oEWIYuAaGp1byKoQEEeXMgFDcUB67HVCdkmLBEoZPNOcYzzm/707CtKRKkmg7cUOOGoCTyPTrZjDhyNkU2pGN7tjUi+KOm9xjg+7cJ8z6UNe22IdpJDVxYFdKioaITXH1Qild1ANnBSZqqELMuKaIXNHnNhdWNQqSQ0M0GNwV3rpS6ADliThyYwxiilLtEMmkxkRrhvwlClRU4j/o0Chf8ucHh1k5oqhZuib8mCMZ0LZO9guvuUszdDewvOrogzpClNekakp+hpWjIJ/zzHWKWDAXwYH9Yz5WYd9X6InPlAp5DHYCFvaHCoaSiRLO+LoHYhK5nORRyxvamf3zUW2ab9Xi5YO6e9Nt4P1AK7+mnmN8cb+i2VjM4OOg5/ZstEw1HsV8GZ541QIpyZb9pcUWFtI7TOMgr39nnrukcJt6QogN7ScrxFV7rsAXLVSXa1LfaeVqKHCwbiISW/UogBWBLPnG12++KJqeK9NZpkSse9Hhfb3yihcy/VfDL0bumWsgEb6eSPZHoeTUdV1TQXy0ZVWL3MVm1WHsfZCFxQwNASDUmwNxne4XySBKpQfZqpzVRdlVFEdoQxn3qXsJpnHCM8z4KumDWxRY/VznWvKozGZjgk+4H/4By/KE3aOzjhP8HPTuhWJjgPeVRZ+xqYiLxXVNS1LD1LMyfvRYhkglNyw7nnfyPadXPkEhPQ8QHpPb3Ew3umN/PLu2DdX1+jKolJlMXuQnJ53uJ9lygXy8bTrsH6PqLCnC46K7K5rZUGDQ1FTWPx9Ertf3uLRtMJLnVHSboVR6WtSEKVKHzdtQL86Ww5VTr4FTjhv1KhVbR+w92y7K+UczpOf+sl0etUWAZzOzHh7YHq3UMsbxhSxbK3QVLF8K6awXChuJxbq1oCxqaoLf9WIDGkMjk+NoLbUNXOlr8EwAA///t9ux6" } diff --git a/x-pack/metricbeat/module/mssql/performance/_meta/data.json b/x-pack/metricbeat/module/mssql/performance/_meta/data.json new file mode 100644 index 00000000000..6384fe9e1ea --- /dev/null +++ b/x-pack/metricbeat/module/mssql/performance/_meta/data.json @@ -0,0 +1,45 @@ +{ + "@timestamp": "2017-10-12T08:05:34.853Z", + "agent": { + "hostname": "host.example.com", + "name": "host.example.com" + }, + "event": { + "dataset": "mssql.performance", + "duration": 115000, + "module": "mssql" + }, + "metricset": { + "name": "performance" + }, + "mssql": { + "performance": { + "batch_requests": { + "sec": 109695 + }, + "buffer_cache_hit": { + "pct": 0.54 + }, + "compilations": { + "sec": 28814 + }, + "lock_waits": { + "sec": 3 + }, + "page_life_expectancy": { + "sec": 31783 + }, + "page_splits": { + "sec": 166 + }, + "recompilations": { + "sec": 0 + }, + "user_connections": 3 + } + }, + "service": { + "address": "172.26.0.2", + "type": "mssql" + } +} \ No newline at end of file diff --git a/x-pack/metricbeat/module/mssql/performance/_meta/docs.asciidoc b/x-pack/metricbeat/module/mssql/performance/_meta/docs.asciidoc new file mode 100644 index 00000000000..f414b12de15 --- /dev/null +++ b/x-pack/metricbeat/module/mssql/performance/_meta/docs.asciidoc @@ -0,0 +1,12 @@ +`performance` Metricset fetches information from what's commonly known as https://docs.microsoft.com/en-us/sql/relational-databases/system-dynamic-management-views/sys-dm-os-performance-counters-transact-sql?view=sql-server-2017[Performance Counters] in MSSQL. + +We fetch the following data: + +* Page splits per instance: Cumulative per instance. Show diffs between periodic readings to identify periods of frequent page splits. +* Page life expectancy in seconds: The expected time in seconds that a data page will remain in the buffer pool. +* Lock wait time in seconds: Cumulative per instance. Show diffs between periodic readings to identify periods of high lock contention. +* Total number of user connections. +* Cumulative (per instance) recompilations time in seconds: Show diffs between periodic readings to identify periods of high SQL re-compilations. +* Compilations time in seconds: Cumulative per instance. Show diffs between periodic readings to identify periods of high SQL compilations. +* Batch requests time in seconds: Cumulative per instance. Show diffs between periodic readings to identify periods of high request activity. +* Buffer Cache hit: Percentage of data pages found in buffer cache without having to read from disk. diff --git a/x-pack/metricbeat/module/mssql/performance/_meta/fields.yml b/x-pack/metricbeat/module/mssql/performance/_meta/fields.yml new file mode 100644 index 00000000000..5fd0a32a88f --- /dev/null +++ b/x-pack/metricbeat/module/mssql/performance/_meta/fields.yml @@ -0,0 +1,63 @@ +- name: performance + type: group + description: performance metricset fetches information about the Performance Counters + fields: + - name: page_splits + type: group + description: Cumulative per instance. Show diffs between periodic readings to identify periods of frequent page splits. + fields: + - name: sec + description: Page splits in seconds + type: long + + - name: page_life_expectancy + type: group + description: The expected time in seconds that a data page will remain in the buffer pool + fields: + - name: sec + description: Page life expectancy in seconds + type: long + + - name: lock_waits + type: group + description: Cumulative per instance. Show diffs between periodic readings to identify periods of high lock contention. + fields: + - name: sec + description: Lock wait time in seconds + type: long + + - name: user_connections + description: Total number of user connections + type: long + + - name: recompilations + type: group + description: Cumulative per instance. Show diffs between periodic readings to identify periods of high SQL re-compilations. + fields: + - name: sec + description: Recompilations time in seconds + type: long + + - name: compilations + type: group + description: Cumulative per instance. Show diffs between periodic readings to identify periods of high SQL compilations. + fields: + - name: sec + description: Compilations time in seconds + type: long + + - name: batch_requests + type: group + description: Cumulative per instance. Show diffs between periodic readings to identify periods of high request activity. + fields: + - name: sec + description: Batch requests time in seconds + type: long + + - name: buffer_cache_hit + type: group + description: Indicates the percentage of pages found in the buffer cache without having to read from disk + fields: + - name: pct + description: The ratio is the total number of cache hits divided by the total number of cache lookups over the last few thousand page accesses + type: double diff --git a/x-pack/metricbeat/module/mssql/performance/data.go b/x-pack/metricbeat/module/mssql/performance/data.go new file mode 100644 index 00000000000..34c2d319fe5 --- /dev/null +++ b/x-pack/metricbeat/module/mssql/performance/data.go @@ -0,0 +1,37 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package performance + +import ( + s "github.com/elastic/beats/libbeat/common/schema" + c "github.com/elastic/beats/libbeat/common/schema/mapstrstr" +) + +var ( + schema = s.Schema{ + "page_splits": s.Object{ + "sec": c.Int("Page Splits/sec", s.Optional), + }, + "page_life_expectancy": s.Object{ + "sec": c.Int("Page life expectancy", s.Optional), + }, + "lock_waits": s.Object{ + "sec": c.Int("Lock Waits/sec", s.Optional), + }, + "user_connections": c.Int("User Connections", s.Optional), + "recompilations": s.Object{ + "sec": c.Int("SQL Re-Compilations/sec", s.Optional), + }, + "compilations": s.Object{ + "sec": c.Int("SQL Compilations/sec", s.Optional), + }, + "batch_requests": s.Object{ + "sec": c.Int("Batch Requests/sec", s.Optional), + }, + "buffer_cache_hit": s.Object{ + "pct": c.Float("Buffer cache hit ratio", s.Optional), + }, + } +) diff --git a/x-pack/metricbeat/module/mssql/performance/data_integration_test.go b/x-pack/metricbeat/module/mssql/performance/data_integration_test.go new file mode 100644 index 00000000000..9c688593783 --- /dev/null +++ b/x-pack/metricbeat/module/mssql/performance/data_integration_test.go @@ -0,0 +1,63 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package performance + +import ( + "net/url" + "testing" + + _ "github.com/denisenkom/go-mssqldb" + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + + mbtest "github.com/elastic/beats/metricbeat/mb/testing" + mtest "github.com/elastic/beats/x-pack/metricbeat/module/mssql/testing" +) + +func TestData(t *testing.T) { + t.Skip("Skipping `data.json` generation test") + _, config, err := getHostURI() + if err != nil { + t.Fatal("error getting config information", err.Error()) + } + + f := mbtest.NewReportingMetricSetV2(t, config) + events, errs := mbtest.ReportingFetchV2(f) + if len(errs) > 0 { + t.Fatalf("Expected 0 error, had %d. %v\n", len(errs), errs) + } + assert.NotEmpty(t, events) + + if err = mbtest.WriteEventsReporterV2(f, t, ""); err != nil { + t.Fatal("write", err) + } +} + +func getHostURI() (string, map[string]interface{}, error) { + config := mtest.GetConfig("performance") + + host, ok := config["hosts"].([]string) + if !ok { + return "", nil, errors.New("error getting host name information") + } + + username, ok := config["username"].(string) + if !ok { + return "", nil, errors.New("error getting username information") + } + + password, ok := config["password"].(string) + if !ok { + return "", nil, errors.New("error getting password information") + } + + u := &url.URL{ + Scheme: "sqlserver", + User: url.UserPassword(username, password), + Host: host[0], + } + + return u.String(), config, nil +} diff --git a/x-pack/metricbeat/module/mssql/performance/performance.go b/x-pack/metricbeat/module/mssql/performance/performance.go new file mode 100644 index 00000000000..26f87735437 --- /dev/null +++ b/x-pack/metricbeat/module/mssql/performance/performance.go @@ -0,0 +1,122 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package performance + +import ( + "database/sql" + "fmt" + "strings" + + "github.com/elastic/beats/libbeat/common" + + "github.com/pkg/errors" + + "github.com/elastic/beats/libbeat/common/cfgwarn" + "github.com/elastic/beats/libbeat/logp" + "github.com/elastic/beats/metricbeat/mb" + "github.com/elastic/beats/x-pack/metricbeat/module/mssql" +) + +type performanceCounter struct { + objectName string + instanceName string + counterName string + counterValue *int64 +} + +// init registers the MetricSet with the central registry as soon as the program +// starts. The New function will be called later to instantiate an instance of +// the MetricSet for each host defined in the module's configuration. After the +// MetricSet has been created then Fetch will begin to be called periodically. +func init() { + mb.Registry.MustAddMetricSet("mssql", "performance", New, + mb.DefaultMetricSet(), + mb.WithHostParser(mssql.HostParser)) +} + +// MetricSet holds any configuration or state information. It must implement +// the mb.MetricSet interface. And this is best achieved by embedding +// mb.BaseMetricSet because it implements all of the required mb.MetricSet +// interface methods except for Fetch. +type MetricSet struct { + mb.BaseMetricSet + log *logp.Logger + fetcher *mssql.Fetcher + db *sql.DB +} + +// New creates a new instance of the MetricSet. New is responsible for unpacking +// any MetricSet specific configuration options if there are any. +func New(base mb.BaseMetricSet) (mb.MetricSet, error) { + cfgwarn.Beta("The mssql performance metricset is beta.") + + logger := logp.NewLogger("mssql.performance").With("host", base.HostData().SanitizedURI) + + db, err := mssql.NewConnection(base.HostData().URI) + if err != nil { + return nil, errors.Wrap(err, "could not create connection to db") + } + + return &MetricSet{ + BaseMetricSet: base, + log: logger, + db: db, + }, nil +} + +// Fetch methods implements the data gathering and data conversion to the right format +// It returns the event which is then forward to the output. In case of an error, a +// descriptive error must be returned. +func (m *MetricSet) Fetch(reporter mb.ReporterV2) { + var err error + var rows *sql.Rows + rows, err = m.db.Query("SELECT object_name, counter_name, instance_name, cntr_value FROM sys.dm_os_performance_counters WHERE counter_name = 'SQL Compilations/sec' OR counter_name = 'SQL Re-Compilations/sec' OR counter_name = 'User Connections' OR counter_name = 'Page splits/sec' OR (counter_name = 'Lock Waits/sec' AND instance_name = '_Total') OR counter_name = 'Page splits/sec' OR (object_name = 'SQLServer:Buffer Manager' AND counter_name = 'Page life expectancy') OR counter_name = 'Batch Requests/sec' OR (counter_name = 'Buffer cache hit ratio' AND object_name = 'SQLServer:Buffer Manager') OR (counter_name = 'Lock Waits/sec' and instance_name = '_Total')") + if err != nil { + reporter.Error(errors.Wrapf(err, "error closing rows")) + return + } + defer func() { + if err := rows.Close(); err != nil { + m.log.Error("error closing rows: %s", err.Error()) + } + }() + + mapStr := common.MapStr{} + for rows.Next() { + var row performanceCounter + if err = rows.Scan(&row.objectName, &row.counterName, &row.instanceName, &row.counterValue); err != nil { + reporter.Error(errors.Wrap(err, "error scanning rows")) + continue + } + + //cell values contains spaces at the beginning and at the end of the 'actual' value. They must be removed. + row.counterName = strings.TrimSpace(row.counterName) + row.instanceName = strings.TrimSpace(row.instanceName) + row.objectName = strings.TrimSpace(row.objectName) + + if row.counterName == "Buffer cache hit ratio" { + mapStr[row.counterName] = fmt.Sprintf("%v", float64(*row.counterValue)/100) + } else { + mapStr[row.counterName] = fmt.Sprintf("%v", *row.counterValue) + } + } + + res, err := schema.Apply(mapStr) + if err != nil { + m.log.Error(errors.Wrap(err, "error applying schema")) + return + } + + if isReported := reporter.Event(mb.Event{ + MetricSetFields: res, + }); !isReported { + m.log.Debug("event not reported") + } +} + +// Close closes the db connection to MS SQL at the Metricset level +func (m *MetricSet) Close() error { + return m.db.Close() +} diff --git a/x-pack/metricbeat/module/mssql/performance/performance_integration_test.go b/x-pack/metricbeat/module/mssql/performance/performance_integration_test.go new file mode 100644 index 00000000000..ad3b10250ec --- /dev/null +++ b/x-pack/metricbeat/module/mssql/performance/performance_integration_test.go @@ -0,0 +1,98 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +// +build integration + +package performance + +import ( + "testing" + + "github.com/elastic/beats/libbeat/logp" + + "github.com/stretchr/testify/assert" + + "github.com/elastic/beats/libbeat/tests/compose" + mbtest "github.com/elastic/beats/metricbeat/mb/testing" + mtest "github.com/elastic/beats/x-pack/metricbeat/module/mssql/testing" +) + +type keyAssertion struct { + key string + assertion func(v interface{}, key string) +} + +func TestFetch(t *testing.T) { + logp.TestingSetup() + compose.EnsureUp(t, "mssql") + + f := mbtest.NewReportingMetricSetV2(t, mtest.GetConfig("performance")) + events, errs := mbtest.ReportingFetchV2(f) + if len(errs) > 0 { + t.Fatalf("Expected 0 error, had %d. %v\n", len(errs), errs) + } + assert.NotEmpty(t, events) + + float64Assertion := func(f func(float64) bool) func(v interface{}, key string) { + return func(v interface{}, key string) { + value, ok := v.(float64) + if !ok { + t.Fatalf("%v is not a float64, but %T", key, v) + } + + assert.Truef(t, f(value), "Value '%d' on field '%s' wasn't higher than 0", value, key) + } + } + + int64Assertion := func(f func(int64) bool) func(v interface{}, key string) { + return func(v interface{}, key string) { + value, ok := v.(int64) + if !ok { + t.Fatalf("%v is not a int64, but %T", key, v) + } + + assert.Truef(t, f(value), "Value '%d' on field '%s' wasn't higher than 0", value, key) + } + } + + int64HigherThanZero := func(v int64) bool { + return v > 0 + } + + int64EqualZero := func(v int64) bool { + return v == 0 + } + + float64HigherThanZero := func(v float64) bool { + return v > 0 + } + + keys := []keyAssertion{ + {key: "page_splits.sec", assertion: int64Assertion(int64HigherThanZero)}, + {key: "page_life_expectancy.sec", assertion: int64Assertion(int64HigherThanZero)}, + {key: "lock_waits.sec", assertion: int64Assertion(int64HigherThanZero)}, + {key: "user_connections", assertion: int64Assertion(int64HigherThanZero)}, + {key: "recompilations.sec", assertion: int64Assertion(int64EqualZero)}, + {key: "compilations.sec", assertion: int64Assertion(int64HigherThanZero)}, + {key: "batch_requests.sec", assertion: int64Assertion(int64HigherThanZero)}, + {key: "buffer_cache_hit.pct", assertion: float64Assertion(float64HigherThanZero)}, + } + for _, keyAssertion := range keys { + var found bool + + for _, event := range events { + value, err := event.MetricSetFields.GetValue(keyAssertion.key) + if err != nil { + continue + } + found = true + + keyAssertion.assertion(value, keyAssertion.key) + } + + if !found { + t.Fatalf("Key '%s' not found", keyAssertion.key) + } + } +} diff --git a/x-pack/metricbeat/modules.d/mssql.yml.disabled b/x-pack/metricbeat/modules.d/mssql.yml.disabled index 4008489e10d..038955a319c 100644 --- a/x-pack/metricbeat/modules.d/mssql.yml.disabled +++ b/x-pack/metricbeat/modules.d/mssql.yml.disabled @@ -1,6 +1,7 @@ - module: mssql metricsets: - "db" + - "performance" hosts: ["sqlserver://sa@localhost"] period: 10s diff --git a/x-pack/metricbeat/tests/system/test_mssql.py b/x-pack/metricbeat/tests/system/test_mssql.py index ca2927f20c6..fdde45931d7 100644 --- a/x-pack/metricbeat/tests/system/test_mssql.py +++ b/x-pack/metricbeat/tests/system/test_mssql.py @@ -45,6 +45,34 @@ def test_status(self): self.assert_fields_are_documented(evt) + @unittest.skipUnless(metricbeat.INTEGRATION_TESTS, "integration test") + @attr('integration') + def test_performance(self): + """ + MSSQL module outputs an event. + """ + self.render_config_template(modules=[{ + "name": "mssql", + "metricsets": ["performance"], + "hosts": self.get_hosts(), + "username": self.get_username(), + "password": self.get_password(), + "period": "5s" + }]) + proc = self.start_beat() + self.wait_until(lambda: self.output_lines() > 0) + proc.check_kill_and_wait() + self.assert_no_logged_warnings() + + output = self.read_output_json() + self.assertEqual(len(output), 1) + evt = output[0] + + self.assertItemsEqual(self.de_dot(MSSQL_FIELDS), evt.keys()) + self.assertTrue(evt["mssql"]["performance"]["page_life_expectancy"]["sec"] > 0) + + self.assert_fields_are_documented(evt) + def get_hosts(self): return [os.getenv('MSSQL_HOST', 'mssql')]