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", - }, - }, - }, - } -}