From 44a1620f9a0f7b38c8946b98df372c2ac9c4d9f4 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Fri, 6 May 2022 23:32:52 -0700 Subject: [PATCH] [receiver/sqlserver] Do not pass metrics details to pkg/winperfcounters This is refactoring of the sqlserver receiver to make it use pkg/winperfcounters just to retrieve required pert counter values and do not pass metrics details back and forth. They are not needed there. This is one of the steps in bigger refactoring of the interface provided by pkg/winperfcounters. --- pkg/winperfcounters/watcher.go | 10 + .../internal/metadata/metrics_builder_ext.go | 64 ----- receiver/sqlserverreceiver/recorders.go | 132 +++++++++ receiver/sqlserverreceiver/scraper.go | 255 ++++-------------- 4 files changed, 192 insertions(+), 269 deletions(-) delete mode 100644 receiver/sqlserverreceiver/internal/metadata/metrics_builder_ext.go create mode 100644 receiver/sqlserverreceiver/recorders.go diff --git a/pkg/winperfcounters/watcher.go b/pkg/winperfcounters/watcher.go index 20b91f0ffa14..7a4202fe30dd 100644 --- a/pkg/winperfcounters/watcher.go +++ b/pkg/winperfcounters/watcher.go @@ -46,6 +46,16 @@ type Watcher struct { MetricRep } +// NewWatcher creates new PerfCounterWatcher by provided parts of its path. +func NewWatcher(object, instance, counterName string) (PerfCounterWatcher, error) { + path := counterPath(object, instance, counterName) + counter, err := pdh.NewPerfCounter(path, true) + if err != nil { + return nil, fmt.Errorf("failed to create perf counter with path %v: %w", path, err) + } + return Watcher{Counter: counter}, nil +} + func (w Watcher) Path() string { return w.Counter.Path() } diff --git a/receiver/sqlserverreceiver/internal/metadata/metrics_builder_ext.go b/receiver/sqlserverreceiver/internal/metadata/metrics_builder_ext.go deleted file mode 100644 index a37983f58de8..000000000000 --- a/receiver/sqlserverreceiver/internal/metadata/metrics_builder_ext.go +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package metadata // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/sqlserverreceiver/internal/metadata" - -import ( - "go.opentelemetry.io/collector/pdata/pcommon" -) - -func (mb *MetricsBuilder) RecordAnyDataPoint(ts pcommon.Timestamp, val float64, name string, attributes map[string]string) { - switch name { - case "sqlserver.user.connection.count": - mb.RecordSqlserverUserConnectionCountDataPoint(ts, int64(val)) - case "sqlserver.batch.request.rate": - mb.RecordSqlserverBatchRequestRateDataPoint(ts, val) - case "sqlserver.batch.sql_compilation.rate": - mb.RecordSqlserverBatchSQLCompilationRateDataPoint(ts, val) - case "sqlserver.batch.sql_recompilation.rate": - mb.RecordSqlserverBatchSQLRecompilationRateDataPoint(ts, val) - case "sqlserver.lock.wait.rate": - mb.RecordSqlserverLockWaitRateDataPoint(ts, val) - case "sqlserver.lock.wait_time.avg": - mb.RecordSqlserverLockWaitTimeAvgDataPoint(ts, val) - case "sqlserver.page.buffer_cache.hit_ratio": - mb.RecordSqlserverPageBufferCacheHitRatioDataPoint(ts, val) - case "sqlserver.page.checkpoint.flush.rate": - mb.RecordSqlserverPageCheckpointFlushRateDataPoint(ts, val) - case "sqlserver.page.lazy_write.rate": - mb.RecordSqlserverPageLazyWriteRateDataPoint(ts, val) - case "sqlserver.page.life_expectancy": - mb.RecordSqlserverPageLifeExpectancyDataPoint(ts, int64(val)) - case "sqlserver.page.operation.rate": - mb.RecordSqlserverPageOperationRateDataPoint(ts, val, MapAttributePageOperations[attributes["type"]]) - case "sqlserver.page.split.rate": - mb.RecordSqlserverPageSplitRateDataPoint(ts, val) - case "sqlserver.transaction_log.flush.data.rate": - mb.RecordSqlserverTransactionLogFlushDataRateDataPoint(ts, val) - case "sqlserver.transaction_log.flush.rate": - mb.RecordSqlserverTransactionLogFlushRateDataPoint(ts, val) - case "sqlserver.transaction_log.flush.wait.rate": - mb.RecordSqlserverTransactionLogFlushWaitRateDataPoint(ts, val) - case "sqlserver.transaction_log.growth.count": - mb.RecordSqlserverTransactionLogGrowthCountDataPoint(ts, int64(val)) - case "sqlserver.transaction_log.shrink.count": - mb.RecordSqlserverTransactionLogShrinkCountDataPoint(ts, int64(val)) - case "sqlserver.transaction_log.usage": - mb.RecordSqlserverTransactionLogUsageDataPoint(ts, int64(val)) - case "sqlserver.transaction.rate": - mb.RecordSqlserverTransactionRateDataPoint(ts, val) - case "sqlserver.transaction.write.rate": - mb.RecordSqlserverTransactionWriteRateDataPoint(ts, val) - } -} diff --git a/receiver/sqlserverreceiver/recorders.go b/receiver/sqlserverreceiver/recorders.go new file mode 100644 index 000000000000..ae08630cc7d4 --- /dev/null +++ b/receiver/sqlserverreceiver/recorders.go @@ -0,0 +1,132 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build windows +// +build windows + +package sqlserverreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/sqlserverreceiver" + +import ( + "go.opentelemetry.io/collector/pdata/pcommon" + + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/sqlserverreceiver/internal/metadata" +) + +type recordFunc = func(*metadata.MetricsBuilder, pcommon.Timestamp, float64) + +type perfCounterRecorderConf struct { + object string + instance string + recorders map[string]recordFunc +} + +// perfCounterRecorders is map of perf counter object -> perf counter name -> value recorder. +var perfCounterRecorders = []perfCounterRecorderConf{ + { + object: "SQLServer:General Statistics", + recorders: map[string]recordFunc{ + "User Connections": func(mb *metadata.MetricsBuilder, ts pcommon.Timestamp, val float64) { + mb.RecordSqlserverUserConnectionCountDataPoint(ts, int64(val)) + }, + }, + }, + { + object: "SQLServer:SQL Statistics", + recorders: map[string]recordFunc{ + "Batch Requests/sec": func(mb *metadata.MetricsBuilder, ts pcommon.Timestamp, val float64) { + mb.RecordSqlserverBatchRequestRateDataPoint(ts, val) + }, + "SQL Compilations/sec": func(mb *metadata.MetricsBuilder, ts pcommon.Timestamp, val float64) { + mb.RecordSqlserverBatchSQLCompilationRateDataPoint(ts, val) + }, + "SQL Re-Compilations/sec": func(mb *metadata.MetricsBuilder, ts pcommon.Timestamp, val float64) { + mb.RecordSqlserverBatchSQLRecompilationRateDataPoint(ts, val) + }, + }, + }, + { + object: "SQLServer:Locks", + instance: "_Total", + recorders: map[string]recordFunc{ + "Lock Waits/sec": func(mb *metadata.MetricsBuilder, ts pcommon.Timestamp, val float64) { + mb.RecordSqlserverLockWaitRateDataPoint(ts, val) + }, + "Average Wait Time (ms)": func(mb *metadata.MetricsBuilder, ts pcommon.Timestamp, val float64) { + mb.RecordSqlserverLockWaitTimeAvgDataPoint(ts, val) + }, + }, + }, + { + object: "SQLServer:Buffer Manager", + recorders: map[string]recordFunc{ + "Buffer cache hit ratio": func(mb *metadata.MetricsBuilder, ts pcommon.Timestamp, val float64) { + mb.RecordSqlserverPageBufferCacheHitRatioDataPoint(ts, val) + }, + "Checkpoint pages/sec": func(mb *metadata.MetricsBuilder, ts pcommon.Timestamp, val float64) { + mb.RecordSqlserverPageCheckpointFlushRateDataPoint(ts, val) + }, + "Lazy Writes/sec": func(mb *metadata.MetricsBuilder, ts pcommon.Timestamp, val float64) { + mb.RecordSqlserverPageLazyWriteRateDataPoint(ts, val) + }, + "Page life expectancy": func(mb *metadata.MetricsBuilder, ts pcommon.Timestamp, val float64) { + mb.RecordSqlserverPageLifeExpectancyDataPoint(ts, int64(val)) + }, + "Page reads/sec": func(mb *metadata.MetricsBuilder, ts pcommon.Timestamp, val float64) { + mb.RecordSqlserverPageOperationRateDataPoint(ts, val, metadata.AttributePageOperationsRead) + }, + "Page writes/sec": func(mb *metadata.MetricsBuilder, ts pcommon.Timestamp, val float64) { + mb.RecordSqlserverPageOperationRateDataPoint(ts, val, metadata.AttributePageOperationsWrite) + }, + }, + }, + { + object: "SQLServer:Access Methods", + instance: "_Total", + recorders: map[string]recordFunc{ + "Page Splits/sec": func(mb *metadata.MetricsBuilder, ts pcommon.Timestamp, val float64) { + mb.RecordSqlserverPageSplitRateDataPoint(ts, val) + }, + }, + }, + { + object: "SQLServer:Databases", + instance: "*", + recorders: map[string]recordFunc{ + "Log Bytes Flushed/sec": func(mb *metadata.MetricsBuilder, ts pcommon.Timestamp, val float64) { + mb.RecordSqlserverTransactionLogFlushDataRateDataPoint(ts, val) + }, + "Log Flushes/sec": func(mb *metadata.MetricsBuilder, ts pcommon.Timestamp, val float64) { + mb.RecordSqlserverTransactionLogFlushRateDataPoint(ts, val) + }, + "Log Flush Waits/sec": func(mb *metadata.MetricsBuilder, ts pcommon.Timestamp, val float64) { + mb.RecordSqlserverTransactionLogFlushWaitRateDataPoint(ts, val) + }, + "Log Growths": func(mb *metadata.MetricsBuilder, ts pcommon.Timestamp, val float64) { + mb.RecordSqlserverTransactionLogGrowthCountDataPoint(ts, int64(val)) + }, + "Log Shrinks": func(mb *metadata.MetricsBuilder, ts pcommon.Timestamp, val float64) { + mb.RecordSqlserverTransactionLogShrinkCountDataPoint(ts, int64(val)) + }, + "Percent Log Used": func(mb *metadata.MetricsBuilder, ts pcommon.Timestamp, val float64) { + mb.RecordSqlserverTransactionLogUsageDataPoint(ts, int64(val)) + }, + "Transactions/sec": func(mb *metadata.MetricsBuilder, ts pcommon.Timestamp, val float64) { + mb.RecordSqlserverTransactionRateDataPoint(ts, val) + }, + "Write Transactions/sec": func(mb *metadata.MetricsBuilder, ts pcommon.Timestamp, val float64) { + mb.RecordSqlserverTransactionWriteRateDataPoint(ts, val) + }, + }, + }, +} diff --git a/receiver/sqlserverreceiver/scraper.go b/receiver/sqlserverreceiver/scraper.go index 784d4b477b79..fcd8a8ca3e06 100644 --- a/receiver/sqlserverreceiver/scraper.go +++ b/receiver/sqlserverreceiver/scraper.go @@ -28,17 +28,26 @@ import ( "go.uber.org/zap" "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/winperfcounters" - windowsapi "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/winperfcounters" "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/sqlserverreceiver/internal/metadata" ) type sqlServerScraper struct { - logger *zap.Logger - config *Config - watchers []winperfcounters.PerfCounterWatcher - metricsBuilder *metadata.MetricsBuilder + logger *zap.Logger + config *Config + watcherRecorders []watcherRecorder + metricsBuilder *metadata.MetricsBuilder } +// watcherRecorder is a struct containing perf counter watcher along with corresponding value recorder. +type watcherRecorder struct { + watcher winperfcounters.PerfCounterWatcher + recorder recordFunc +} + +// curriedRecorder is a recorder function that already has value to be recorded, +// it needs metadata.MetricsBuilder and timestamp as arguments. +type curriedRecorder func(*metadata.MetricsBuilder, pcommon.Timestamp) + // newSqlServerScraper returns a new sqlServerScraper. func newSqlServerScraper(logger *zap.Logger, cfg *Config) *sqlServerScraper { metricsBuilder := metadata.NewMetricsBuilder(cfg.Metrics) @@ -47,61 +56,70 @@ func newSqlServerScraper(logger *zap.Logger, cfg *Config) *sqlServerScraper { // start creates and sets the watchers for the scraper. func (s *sqlServerScraper) start(ctx context.Context, host component.Host) error { - watchers := []winperfcounters.PerfCounterWatcher{} - for _, objCfg := range createWatcherConfigs() { - objWatchers, err := objCfg.BuildPaths() - if err != nil { - s.logger.Warn("some performance counters could not be initialized", zap.Error(err)) - } - for _, objWatcher := range objWatchers { - watchers = append(watchers, objWatcher) + s.watcherRecorders = []watcherRecorder{} + + for _, pcr := range perfCounterRecorders { + for perfCounterName, recorder := range pcr.recorders { + w, err := winperfcounters.NewWatcher(pcr.object, pcr.instance, perfCounterName) + if err != nil { + s.logger.Warn(err.Error()) + continue + } + s.watcherRecorders = append(s.watcherRecorders, watcherRecorder{w, recorder}) } } - s.watchers = watchers return nil } // scrape collects windows performance counter data from all watchers and then records/emits it using the metricBuilder func (s *sqlServerScraper) scrape(ctx context.Context) (pmetric.Metrics, error) { - metricsByDatabase, errs := createMetricGroupPerDatabase(s.watchers) + recordersByDatabase, errs := recordersPerDatabase(s.watcherRecorders) - for key, metricGroup := range metricsByDatabase { - s.emitMetricGroup(metricGroup, key) + for dbName, recorders := range recordersByDatabase { + s.emitMetricGroup(recorders, dbName) } return s.metricsBuilder.Emit(), errs } -func createMetricGroupPerDatabase(watchers []windowsapi.PerfCounterWatcher) (map[string][]winperfcounters.CounterValue, error) { +// recordersPerDatabase scrapes perf counter values using provided []watcherRecorder and returns +// a map of database name to curriedRecorder that includes the recorded value in its closure. +func recordersPerDatabase(watcherRecorders []watcherRecorder) (map[string][]curriedRecorder, error) { var errs error - metricsByDatabase := map[string][]winperfcounters.CounterValue{} - for _, watcher := range watchers { - counterValues, err := watcher.ScrapeData() + dbToRecorders := make(map[string][]curriedRecorder) + for _, wr := range watcherRecorders { + counterValues, err := wr.watcher.ScrapeData() if err != nil { errs = multierr.Append(errs, err) continue } + for _, counterValue := range counterValues { - key := counterValue.Attributes["instance"] + dbName := counterValue.Attributes["instance"] - if metricsByDatabase[key] == nil { - metricsByDatabase[key] = []winperfcounters.CounterValue{counterValue} - } else { - metricsByDatabase[key] = append(metricsByDatabase[key], counterValue) + // it's important to initialize new values for the closure. + val := counterValue.Value + recorder := wr.recorder + + if _, ok := dbToRecorders[dbName]; !ok { + dbToRecorders[dbName] = []curriedRecorder{} } + dbToRecorders[dbName] = append(dbToRecorders[dbName], func(mb *metadata.MetricsBuilder, ts pcommon.Timestamp) { + recorder(mb, ts, val) + }) } } - return metricsByDatabase, errs + return dbToRecorders, errs } -func (s *sqlServerScraper) emitMetricGroup(metricGroup []winperfcounters.CounterValue, databaseName string) { +func (s *sqlServerScraper) emitMetricGroup(recorders []curriedRecorder, databaseName string) { now := pcommon.NewTimestampFromTime(time.Now()) - for _, metric := range metricGroup { - s.metricsBuilder.RecordAnyDataPoint(now, metric.Value, metric.MetricRep.Name, metric.MetricRep.Attributes) + for _, recorder := range recorders { + recorder(s.metricsBuilder, now) } if databaseName != "" { @@ -116,184 +134,11 @@ func (s *sqlServerScraper) emitMetricGroup(metricGroup []winperfcounters.Counter // shutdown stops all of the watchers for the scraper. func (s sqlServerScraper) shutdown(ctx context.Context) error { var errs error - for _, watcher := range s.watchers { - err := watcher.Close() + for _, wr := range s.watcherRecorders { + err := wr.watcher.Close() if err != nil { errs = multierr.Append(errs, err) } } return errs } - -// createWatcherConfigs returns established performance counter configs for each metric. -func createWatcherConfigs() []windowsapi.ObjectConfig { - return []windowsapi.ObjectConfig{ - { - Object: "SQLServer:General Statistics", - Counters: []windowsapi.CounterConfig{ - { - MetricRep: windowsapi.MetricRep{ - Name: "sqlserver.user.connection.count", - }, - Name: "User Connections", - }, - }, - }, - { - Object: "SQLServer:SQL Statistics", - Counters: []windowsapi.CounterConfig{ - { - MetricRep: windowsapi.MetricRep{ - Name: "sqlserver.batch.request.rate", - }, - - Name: "Batch Requests/sec", - }, - { - MetricRep: windowsapi.MetricRep{ - Name: "sqlserver.batch.sql_compilation.rate", - }, - - Name: "SQL Compilations/sec", - }, - { - MetricRep: windowsapi.MetricRep{ - Name: "sqlserver.batch.sql_recompilation.rate", - }, - Name: "SQL Re-Compilations/sec", - }, - }, - }, - { - Object: "SQLServer:Locks", - Instances: []string{"_Total"}, - Counters: []windowsapi.CounterConfig{ - { - MetricRep: windowsapi.MetricRep{ - Name: "sqlserver.lock.wait.rate", - }, - Name: "Lock Waits/sec", - }, - { - MetricRep: windowsapi.MetricRep{ - Name: "sqlserver.lock.wait_time.avg", - }, - Name: "Average Wait Time (ms)", - }, - }, - }, - { - Object: "SQLServer:Buffer Manager", - Counters: []windowsapi.CounterConfig{ - { - MetricRep: windowsapi.MetricRep{ - Name: "sqlserver.page.buffer_cache.hit_ratio", - }, - Name: "Buffer cache hit ratio", - }, - { - MetricRep: windowsapi.MetricRep{ - Name: "sqlserver.page.checkpoint.flush.rate", - }, - Name: "Checkpoint pages/sec", - }, - { - MetricRep: windowsapi.MetricRep{ - Name: "sqlserver.page.lazy_write.rate", - }, - Name: "Lazy Writes/sec", - }, - { - MetricRep: windowsapi.MetricRep{ - Name: "sqlserver.page.life_expectancy", - }, - Name: "Page life expectancy", - }, - { - MetricRep: windowsapi.MetricRep{ - Name: "sqlserver.page.operation.rate", - Attributes: map[string]string{ - "type": "read", - }, - }, - Name: "Page reads/sec", - }, - { - MetricRep: windowsapi.MetricRep{ - Name: "sqlserver.page.operation.rate", - Attributes: map[string]string{ - "type": "write", - }, - }, - Name: "Page writes/sec", - }, - }, - }, - { - Object: "SQLServer:Access Methods", - Instances: []string{"_Total"}, - Counters: []windowsapi.CounterConfig{ - { - MetricRep: windowsapi.MetricRep{ - Name: "sqlserver.page.split.rate", - }, - Name: "Page Splits/sec", - }, - }, - }, - { - Object: "SQLServer:Databases", - Instances: []string{"*"}, - Counters: []windowsapi.CounterConfig{ - { - MetricRep: windowsapi.MetricRep{ - Name: "sqlserver.transaction_log.flush.data.rate", - }, - Name: "Log Bytes Flushed/sec", - }, - { - MetricRep: windowsapi.MetricRep{ - Name: "sqlserver.transaction_log.flush.rate", - }, - Name: "Log Flushes/sec", - }, - { - MetricRep: windowsapi.MetricRep{ - Name: "sqlserver.transaction_log.flush.wait.rate", - }, - Name: "Log Flush Waits/sec", - }, - { - MetricRep: windowsapi.MetricRep{ - Name: "sqlserver.transaction_log.growth.count", - }, - Name: "Log Growths", - }, - { - MetricRep: windowsapi.MetricRep{ - Name: "sqlserver.transaction_log.shrink.count", - }, - Name: "Log Shrinks", - }, - { - MetricRep: windowsapi.MetricRep{ - Name: "sqlserver.transaction_log.usage", - }, - Name: "Percent Log Used", - }, - { - MetricRep: windowsapi.MetricRep{ - Name: "sqlserver.transaction.rate", - }, - Name: "Transactions/sec", - }, - { - MetricRep: windowsapi.MetricRep{ - Name: "sqlserver.transaction.write.rate", - }, - Name: "Write Transactions/sec", - }, - }, - }, - } -}