From 17bb040f1962311ef0c9ff5854e57d5a4d2ca5f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-Otto=20Kr=C3=B6pke?= Date: Sun, 1 Dec 2024 14:38:54 +0100 Subject: [PATCH] mssql: add counter based on server version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jan-Otto Kröpke --- docs/collector.mssql.md | 6 +- internal/collector/mssql/mssql.go | 152 ++++-------------- .../collector/mssql/mssql_access_methods.go | 6 +- .../mssql/mssql_availability_replica.go | 6 +- .../collector/mssql/mssql_buffer_manager.go | 6 +- internal/collector/mssql/mssql_database.go | 25 +-- .../collector/mssql/mssql_database_replica.go | 6 +- .../mssql/mssql_general_statistics.go | 6 +- internal/collector/mssql/mssql_instance.go | 52 ++++++ internal/collector/mssql/mssql_locks.go | 6 +- .../collector/mssql/mssql_memory_manager.go | 6 +- internal/collector/mssql/mssql_sql_errors.go | 6 +- internal/collector/mssql/mssql_sql_stats.go | 6 +- .../collector/mssql/mssql_transactions.go | 6 +- internal/collector/mssql/mssql_wait_stats.go | 6 +- internal/collector/mssql/types.go | 112 +++++++++++++ 16 files changed, 241 insertions(+), 172 deletions(-) create mode 100644 internal/collector/mssql/mssql_instance.go create mode 100644 internal/collector/mssql/types.go diff --git a/docs/collector.mssql.md b/docs/collector.mssql.md index 61fc47b32..9ccf6042e 100644 --- a/docs/collector.mssql.md +++ b/docs/collector.mssql.md @@ -18,15 +18,10 @@ Comma-separated list of MSSQL WMI classes to use. Supported values are `accessme If true, print available mssql WMI classes and exit. Only displays if the mssql collector is enabled.fman`, `databases`, `dbreplica`, `genstats`, `locks`, `memmgr`, `sqlstats`, `sqlerrors`, `transactions`, and `waitstats`. -### `--collector.mssql.port` - -Port of MSSQL server used for `windows_mssql_info` metric. Default is `1433`. - ## Metrics | Name | Description | Type | Labels | |--------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|-------------------------------| -| `windows_mssql_info` | Returns information about the MSSQL server running on port 1433 | gauge | `version` | | `windows_mssql_collector_duration_seconds` | The time taken for each sub-collector to return | gauge | `collector`, `mssql_instance` | | `windows_mssql_collector_success` | 1 if sub-collector succeeded, 0 otherwise | gauge | `collector`, `mssql_instance` | | `windows_mssql_accessmethods_au_batch_cleanups` | The total number of batches that were completed successfully by the background task that cleans up deferred dropped allocation units | counter | `mssql_instance` | @@ -197,6 +192,7 @@ Port of MSSQL server used for `windows_mssql_info` metric. Default is `1433`. | `windows_mssql_genstats_trace_event_notification_queue_size` | Number of trace event notification instances waiting in the internal queue to be sent through Service Broker | gauge | `mssql_instance` | | `windows_mssql_genstats_transactions` | Number of transaction enlistments (local, DTC, bound all combined) | gauge | `mssql_instance` | | `windows_mssql_genstats_user_connections` | Counts the number of users currently connected to SQL Server | gauge | `mssql_instance` | +| `windows_mssql_instance_info ` | Returns information about the MSSQL server running on port 1433 | gauge | `version` | | `windows_mssql_locks_average_wait_seconds` | Average amount of wait time (in milliseconds) for each lock request that resulted in a wait | gauge | `mssql_instance`, `resource` | | `windows_mssql_locks_lock_requests` | Number of new locks and lock conversions per second requested from the lock manager | counter | `mssql_instance`, `resource` | | `windows_mssql_locks_lock_timeouts` | Number of lock requests per second that timed out, including requests for NOWAIT locks | counter | `mssql_instance`, `resource` | diff --git a/internal/collector/mssql/mssql.go b/internal/collector/mssql/mssql.go index 0fac93ea1..b5382111f 100644 --- a/internal/collector/mssql/mssql.go +++ b/internal/collector/mssql/mssql.go @@ -20,20 +20,15 @@ import ( "fmt" "log/slog" "sort" - "strconv" "strings" "sync" "time" - "unsafe" - "github.com/Microsoft/go-winio/pkg/process" "github.com/alecthomas/kingpin/v2" - "github.com/prometheus-community/windows_exporter/internal/headers/iphlpapi" "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/perfdata" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus/client_golang/prometheus" - "golang.org/x/sys/windows" "golang.org/x/sys/windows/registry" ) @@ -46,6 +41,7 @@ const ( subCollectorDatabases = "databases" subCollectorDatabaseReplica = "dbreplica" subCollectorGeneralStatistics = "genstats" + subCollectorInfo = "info" subCollectorLocks = "locks" subCollectorMemoryManager = "memmgr" subCollectorSQLErrors = "sqlerrors" @@ -56,7 +52,6 @@ const ( type Config struct { CollectorsEnabled []string `yaml:"collectors_enabled"` - Port uint16 `yaml:"port"` } //nolint:gochecknoglobals @@ -68,6 +63,7 @@ var ConfigDefaults = Config{ subCollectorDatabases, subCollectorDatabaseReplica, subCollectorGeneralStatistics, + subCollectorInfo, subCollectorLocks, subCollectorMemoryManager, subCollectorSQLErrors, @@ -75,7 +71,6 @@ var ConfigDefaults = Config{ subCollectorTransactions, subCollectorWaitStats, }, - Port: 1433, } // A Collector is a Prometheus Collector for various WMI Win32_PerfRawData_MSSQLSERVER_* metrics. @@ -84,17 +79,13 @@ type Collector struct { logger *slog.Logger - mssqlInstances mssqlInstancesType + mssqlInstances []mssqlInstance collectorFns []func(ch chan<- prometheus.Metric) error closeFns []func() - fileVersion string - productVersion string - // meta mssqlScrapeDurationDesc *prometheus.Desc mssqlScrapeSuccessDesc *prometheus.Desc - mssqlInfoDesc *prometheus.Desc collectorAccessMethods collectorAvailabilityReplica @@ -102,6 +93,7 @@ type Collector struct { collectorDatabaseReplica collectorDatabases collectorGeneralStatistics + collectorInstance collectorLocks collectorMemoryManager collectorSQLErrors @@ -110,8 +102,6 @@ type Collector struct { collectorWaitStats } -type mssqlInstancesType map[string]string - func New(config *Config) *Collector { if config == nil { config = &ConfigDefaults @@ -121,10 +111,6 @@ func New(config *Config) *Collector { config.CollectorsEnabled = ConfigDefaults.CollectorsEnabled } - if config.Port == 0 { - config.Port = ConfigDefaults.Port - } - c := &Collector{ config: *config, } @@ -144,11 +130,6 @@ func NewWithFlags(app *kingpin.Application) *Collector { "Comma-separated list of collectors to use.", ).Default(strings.Join(c.config.CollectorsEnabled, ",")).StringVar(&collectorsEnabled) - app.Flag( - "collector.mssql.port", - "Port of MSSQL server used for windows_mssql_info metric.", - ).Default(strconv.FormatUint(uint64(c.config.Port), 10)).Uint16Var(&c.config.Port) - app.Action(func(*kingpin.ParseContext) error { c.config.CollectorsEnabled = strings.Split(collectorsEnabled, ",") @@ -172,18 +153,13 @@ func (c *Collector) Close() error { func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error { c.logger = logger.With(slog.String("collector", Name)) - c.mssqlInstances = c.getMSSQLInstances() - fileVersion, productVersion, err := c.getMSSQLServerVersion(c.config.Port) + instances, err := c.getMSSQLInstances() if err != nil { - logger.Warn("failed to get MSSQL server version", - slog.Any("err", err), - slog.String("collector", Name), - ) + return fmt.Errorf("couldn't get SQL instances: %w", err) } - c.fileVersion = fileVersion - c.productVersion = productVersion + c.mssqlInstances = instances subCollectors := map[string]struct { build func() error @@ -220,6 +196,11 @@ func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error { collect: c.collectGeneralStatistics, close: c.closeGeneralStatistics, }, + subCollectorInfo: { + build: c.buildInstance, + collect: c.collectInstance, + close: c.closeInstance, + }, subCollectorLocks: { build: c.buildLocks, collect: c.collectLocks, @@ -272,14 +253,6 @@ func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error { c.closeFns = append(c.closeFns, subCollectors[name].close) } - // meta - c.mssqlInfoDesc = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "info"), - "mssql server information", - []string{"file_version", "version"}, - nil, - ) - c.mssqlScrapeDurationDesc = prometheus.NewDesc( prometheus.BuildFQName(types.Namespace, Name, "collector_duration_seconds"), "windows_exporter: Duration of an mssql child collection.", @@ -326,22 +299,12 @@ func (c *Collector) Collect(ch chan<- prometheus.Metric) error { return errors.Join(errs...) } -func (c *Collector) getMSSQLInstances() mssqlInstancesType { - sqlInstances := make(mssqlInstancesType) - - // in case querying the registry fails, return the default instance - sqlDefaultInstance := make(mssqlInstancesType) - sqlDefaultInstance["MSSQLSERVER"] = "" - +func (c *Collector) getMSSQLInstances() ([]mssqlInstance, error) { regKey := `Software\Microsoft\Microsoft SQL Server\Instance Names\SQL` k, err := registry.OpenKey(registry.LOCAL_MACHINE, regKey, registry.QUERY_VALUE) if err != nil { - c.logger.Warn("couldn't open registry to determine SQL instances", - slog.Any("err", err), - ) - - return sqlDefaultInstance + return nil, fmt.Errorf("couldn't open registry to determine SQL instances: %w", err) } defer func(key registry.Key) { @@ -354,22 +317,28 @@ func (c *Collector) getMSSQLInstances() mssqlInstancesType { instanceNames, err := k.ReadValueNames(0) if err != nil { - c.logger.Warn("can't ReadSubKeyNames", - slog.Any("err", err), - ) - - return sqlDefaultInstance + return nil, fmt.Errorf("couldn't read subkey names: %w", err) } + sqlInstances := make([]mssqlInstance, 0, len(instanceNames)) + for _, instanceName := range instanceNames { - if instanceVersion, _, err := k.GetStringValue(instanceName); err == nil { - sqlInstances[instanceName] = instanceVersion + instanceVersion, _, err := k.GetStringValue(instanceName) + if err != nil { + return nil, fmt.Errorf("couldn't get instance info: %w", err) + } + + instance, err := newMssqlInstance(instanceVersion) + if err != nil { + return nil, err } + + sqlInstances = append(sqlInstances, instance) } c.logger.Debug(fmt.Sprintf("detected MSSQL Instances: %#v\n", sqlInstances)) - return sqlInstances + return sqlInstances, nil } // mssqlGetPerfObjectName returns the name of the Windows Performance @@ -433,68 +402,3 @@ func (c *Collector) collect( return errors.Join(errs...) } - -// getMSSQLServerVersion get the version of the SQL Server instance by -// reading the version information from the process running the SQL Server instance port. -func (c *Collector) getMSSQLServerVersion(port uint16) (string, string, error) { - pid, err := iphlpapi.GetOwnerPIDOfTCPPort(windows.AF_INET, port) - if err != nil { - return "", "", fmt.Errorf("failed to get the PID of the process running on port 1433: %w", err) - } - - hProcess, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, pid) - if err != nil { - return "", "", fmt.Errorf("failed to open the process with PID %d: %w", pid, err) - } - - defer windows.CloseHandle(hProcess) //nolint:errcheck - - processFilePath, err := process.QueryFullProcessImageName(hProcess, process.ImageNameFormatWin32Path) - if err != nil { - return "", "", fmt.Errorf("failed to query the full path of the process with PID %d: %w", pid, err) - } - - // Load the file version information - size, err := windows.GetFileVersionInfoSize(processFilePath, nil) - if err != nil { - return "", "", fmt.Errorf("failed to get the size of the file version information: %w", err) - } - - fileVersionInfo := make([]byte, size) - - err = windows.GetFileVersionInfo(processFilePath, 0, size, unsafe.Pointer(&fileVersionInfo[0])) - if err != nil { - return "", "", fmt.Errorf("failed to get the file version information: %w", err) - } - - var ( - verData *byte - verSize uint32 - ) - - err = windows.VerQueryValue( - unsafe.Pointer(&fileVersionInfo[0]), - `\StringFileInfo\040904b0\ProductVersion`, - unsafe.Pointer(&verData), - &verSize, - ) - if err != nil { - return "", "", fmt.Errorf("failed to query the product version: %w", err) - } - - productVersion := windows.UTF16ToString((*[1 << 16]uint16)(unsafe.Pointer(verData))[:verSize]) - - err = windows.VerQueryValue( - unsafe.Pointer(&fileVersionInfo[0]), - `\StringFileInfo\040904b0\FileVersion`, - unsafe.Pointer(&verData), - &verSize, - ) - if err != nil { - return "", "", fmt.Errorf("failed to query the file version: %w", err) - } - - fileVersion := windows.UTF16ToString((*[1 << 16]uint16)(unsafe.Pointer(verData))[:verSize]) - - return fileVersion, productVersion, nil -} diff --git a/internal/collector/mssql/mssql_access_methods.go b/internal/collector/mssql/mssql_access_methods.go index df24923a0..b37a56b6b 100644 --- a/internal/collector/mssql/mssql_access_methods.go +++ b/internal/collector/mssql/mssql_access_methods.go @@ -172,10 +172,10 @@ func (c *Collector) buildAccessMethods() error { accessMethodsWorktablesFromCacheRatioBase, } - for sqlInstance := range c.mssqlInstances { - c.accessMethodsPerfDataCollectors[sqlInstance], err = perfdata.NewCollector(c.mssqlGetPerfObjectName(sqlInstance, "Access Methods"), nil, counters) + for _, sqlInstance := range c.mssqlInstances { + c.accessMethodsPerfDataCollectors[sqlInstance.name], err = perfdata.NewCollector(c.mssqlGetPerfObjectName(sqlInstance.name, "Access Methods"), nil, counters) if err != nil { - errs = append(errs, fmt.Errorf("failed to create AccessMethods collector for instance %s: %w", sqlInstance, err)) + errs = append(errs, fmt.Errorf("failed to create AccessMethods collector for instance %s: %w", sqlInstance.name, err)) } } diff --git a/internal/collector/mssql/mssql_availability_replica.go b/internal/collector/mssql/mssql_availability_replica.go index b48185482..0817e11b4 100644 --- a/internal/collector/mssql/mssql_availability_replica.go +++ b/internal/collector/mssql/mssql_availability_replica.go @@ -68,10 +68,10 @@ func (c *Collector) buildAvailabilityReplica() error { availReplicaSendsToTransportPerSec, } - for sqlInstance := range c.mssqlInstances { - c.availabilityReplicaPerfDataCollectors[sqlInstance], err = perfdata.NewCollector(c.mssqlGetPerfObjectName(sqlInstance, "Availability Replica"), perfdata.InstancesAll, counters) + for _, sqlInstance := range c.mssqlInstances { + c.availabilityReplicaPerfDataCollectors[sqlInstance.name], err = perfdata.NewCollector(c.mssqlGetPerfObjectName(sqlInstance.name, "Availability Replica"), perfdata.InstancesAll, counters) if err != nil { - errs = append(errs, fmt.Errorf("failed to create Availability Replica collector for instance %s: %w", sqlInstance, err)) + errs = append(errs, fmt.Errorf("failed to create Availability Replica collector for instance %s: %w", sqlInstance.name, err)) } } diff --git a/internal/collector/mssql/mssql_buffer_manager.go b/internal/collector/mssql/mssql_buffer_manager.go index 152f8cc6d..12e297c0f 100644 --- a/internal/collector/mssql/mssql_buffer_manager.go +++ b/internal/collector/mssql/mssql_buffer_manager.go @@ -109,10 +109,10 @@ func (c *Collector) buildBufferManager() error { bufManTargetPages, } - for sqlInstance := range c.mssqlInstances { - c.bufManPerfDataCollectors[sqlInstance], err = perfdata.NewCollector(c.mssqlGetPerfObjectName(sqlInstance, "Buffer Manager"), nil, counters) + for _, sqlInstance := range c.mssqlInstances { + c.bufManPerfDataCollectors[sqlInstance.name], err = perfdata.NewCollector(c.mssqlGetPerfObjectName(sqlInstance.name, "Buffer Manager"), nil, counters) if err != nil { - errs = append(errs, fmt.Errorf("failed to create Buffer Manager collector for instance %s: %w", sqlInstance, err)) + errs = append(errs, fmt.Errorf("failed to create Buffer Manager collector for instance %s: %w", sqlInstance.name, err)) } } diff --git a/internal/collector/mssql/mssql_database.go b/internal/collector/mssql/mssql_database.go index def6e7160..c81fd8c6f 100644 --- a/internal/collector/mssql/mssql_database.go +++ b/internal/collector/mssql/mssql_database.go @@ -134,7 +134,6 @@ func (c *Collector) buildDatabases() error { c.databasesPerfDataCollectors = make(map[string]*perfdata.Collector, len(c.mssqlInstances)) errs := make([]error, 0, len(c.mssqlInstances)) counters := []string{ - databasesActiveParallelRedoThreads, databasesActiveTransactions, databasesBackupPerRestoreThroughputPerSec, databasesBulkCopyRowsPerSec, @@ -184,10 +183,14 @@ func (c *Collector) buildDatabases() error { databasesXTPMemoryUsedKB, } - for sqlInstance := range c.mssqlInstances { - c.databasesPerfDataCollectors[sqlInstance], err = perfdata.NewCollector(c.mssqlGetPerfObjectName(sqlInstance, "Databases"), perfdata.InstancesAll, counters) + for _, sqlInstance := range c.mssqlInstances { + if sqlInstance.isGreaterOrEqualThan(serverVersion2019) { + counters = append(counters, databasesActiveParallelRedoThreads) + } + + c.databasesPerfDataCollectors[sqlInstance.name], err = perfdata.NewCollector(c.mssqlGetPerfObjectName(sqlInstance.name, "Databases"), perfdata.InstancesAll, counters) if err != nil { - errs = append(errs, fmt.Errorf("failed to create Databases collector for instance %s: %w", sqlInstance, err)) + errs = append(errs, fmt.Errorf("failed to create Databases collector for instance %s: %w", sqlInstance.name, err)) } } @@ -498,12 +501,14 @@ func (c *Collector) collectDatabasesInstance(ch chan<- prometheus.Metric, sqlIns } for dbName, data := range perfData { - ch <- prometheus.MustNewConstMetric( - c.databasesActiveParallelRedoThreads, - prometheus.GaugeValue, - data[databasesActiveParallelRedoThreads].FirstValue, - sqlInstance, dbName, - ) + if counter, ok := data[databasesActiveParallelRedoThreads]; ok { + ch <- prometheus.MustNewConstMetric( + c.databasesActiveParallelRedoThreads, + prometheus.GaugeValue, + counter.FirstValue, + sqlInstance, dbName, + ) + } ch <- prometheus.MustNewConstMetric( c.databasesActiveTransactions, diff --git a/internal/collector/mssql/mssql_database_replica.go b/internal/collector/mssql/mssql_database_replica.go index e12375c85..d89f50856 100644 --- a/internal/collector/mssql/mssql_database_replica.go +++ b/internal/collector/mssql/mssql_database_replica.go @@ -112,10 +112,10 @@ func (c *Collector) buildDatabaseReplica() error { dbReplicaTransactionDelay, } - for sqlInstance := range c.mssqlInstances { - c.dbReplicaPerfDataCollectors[sqlInstance], err = perfdata.NewCollector(c.mssqlGetPerfObjectName(sqlInstance, "Database Replica"), perfdata.InstancesAll, counters) + for _, sqlInstance := range c.mssqlInstances { + c.dbReplicaPerfDataCollectors[sqlInstance.name], err = perfdata.NewCollector(c.mssqlGetPerfObjectName(sqlInstance.name, "Database Replica"), perfdata.InstancesAll, counters) if err != nil { - errs = append(errs, fmt.Errorf("failed to create Database Replica collector for instance %s: %w", sqlInstance, err)) + errs = append(errs, fmt.Errorf("failed to create Database Replica collector for instance %s: %w", sqlInstance.name, err)) } } diff --git a/internal/collector/mssql/mssql_general_statistics.go b/internal/collector/mssql/mssql_general_statistics.go index d092bb4c0..4a8248606 100644 --- a/internal/collector/mssql/mssql_general_statistics.go +++ b/internal/collector/mssql/mssql_general_statistics.go @@ -112,10 +112,10 @@ func (c *Collector) buildGeneralStatistics() error { genStatsUserConnections, } - for sqlInstance := range c.mssqlInstances { - c.genStatsPerfDataCollectors[sqlInstance], err = perfdata.NewCollector(c.mssqlGetPerfObjectName(sqlInstance, "General Statistics"), nil, counters) + for _, sqlInstance := range c.mssqlInstances { + c.genStatsPerfDataCollectors[sqlInstance.name], err = perfdata.NewCollector(c.mssqlGetPerfObjectName(sqlInstance.name, "General Statistics"), nil, counters) if err != nil { - errs = append(errs, fmt.Errorf("failed to create General Statistics collector for instance %s: %w", sqlInstance, err)) + errs = append(errs, fmt.Errorf("failed to create General Statistics collector for instance %s: %w", sqlInstance.name, err)) } } diff --git a/internal/collector/mssql/mssql_instance.go b/internal/collector/mssql/mssql_instance.go new file mode 100644 index 000000000..cc29022d3 --- /dev/null +++ b/internal/collector/mssql/mssql_instance.go @@ -0,0 +1,52 @@ +// Copyright 2024 The Prometheus 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 + +package mssql + +import ( + "github.com/prometheus-community/windows_exporter/internal/types" + "github.com/prometheus/client_golang/prometheus" +) + +type collectorInstance struct { + instances *prometheus.GaugeVec +} + +func (c *Collector) buildInstance() error { + c.instances = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Namespace: types.Namespace, + Subsystem: Name, + Name: "instance_info", + Help: "A metric with a constant '1' value labeled with mssql instance information", + }, + []string{"edition", "mssql_instance", "patch", "version"}, + ) + + for _, instance := range c.mssqlInstances { + c.instances.WithLabelValues(instance.edition, instance.name, instance.patchVersion, instance.majorVersion.String()).Set(1) + } + + return nil +} + +func (c *Collector) collectInstance(ch chan<- prometheus.Metric) error { + c.instances.Collect(ch) + + return nil +} + +func (c *Collector) closeInstance() { +} diff --git a/internal/collector/mssql/mssql_locks.go b/internal/collector/mssql/mssql_locks.go index 81864ff5b..503befd67 100644 --- a/internal/collector/mssql/mssql_locks.go +++ b/internal/collector/mssql/mssql_locks.go @@ -65,10 +65,10 @@ func (c *Collector) buildLocks() error { locksNumberOfDeadlocksPerSec, } - for sqlInstance := range c.mssqlInstances { - c.locksPerfDataCollectors[sqlInstance], err = perfdata.NewCollector(c.mssqlGetPerfObjectName(sqlInstance, "Locks"), perfdata.InstancesAll, counters) + for _, sqlInstance := range c.mssqlInstances { + c.locksPerfDataCollectors[sqlInstance.name], err = perfdata.NewCollector(c.mssqlGetPerfObjectName(sqlInstance.name, "Locks"), perfdata.InstancesAll, counters) if err != nil { - errs = append(errs, fmt.Errorf("failed to create Locks collector for instance %s: %w", sqlInstance, err)) + errs = append(errs, fmt.Errorf("failed to create Locks collector for instance %s: %w", sqlInstance.name, err)) } } diff --git a/internal/collector/mssql/mssql_memory_manager.go b/internal/collector/mssql/mssql_memory_manager.go index e2138cbe3..9d397629c 100644 --- a/internal/collector/mssql/mssql_memory_manager.go +++ b/internal/collector/mssql/mssql_memory_manager.go @@ -100,10 +100,10 @@ func (c *Collector) buildMemoryManager() error { memMgrTotalServerMemoryKB, } - for sqlInstance := range c.mssqlInstances { - c.memMgrPerfDataCollectors[sqlInstance], err = perfdata.NewCollector(c.mssqlGetPerfObjectName(sqlInstance, "Memory Manager"), perfdata.InstancesAll, counters) + for _, sqlInstance := range c.mssqlInstances { + c.memMgrPerfDataCollectors[sqlInstance.name], err = perfdata.NewCollector(c.mssqlGetPerfObjectName(sqlInstance.name, "Memory Manager"), perfdata.InstancesAll, counters) if err != nil { - errs = append(errs, fmt.Errorf("failed to create Memory Manager collector for instance %s: %w", sqlInstance, err)) + errs = append(errs, fmt.Errorf("failed to create Memory Manager collector for instance %s: %w", sqlInstance.name, err)) } } diff --git a/internal/collector/mssql/mssql_sql_errors.go b/internal/collector/mssql/mssql_sql_errors.go index eb7184f81..8f4392c51 100644 --- a/internal/collector/mssql/mssql_sql_errors.go +++ b/internal/collector/mssql/mssql_sql_errors.go @@ -44,10 +44,10 @@ func (c *Collector) buildSQLErrors() error { sqlErrorsErrorsPerSec, } - for sqlInstance := range c.mssqlInstances { - c.sqlErrorsPerfDataCollectors[sqlInstance], err = perfdata.NewCollector(c.mssqlGetPerfObjectName(sqlInstance, "SQL Errors"), perfdata.InstancesAll, counters) + for _, sqlInstance := range c.mssqlInstances { + c.sqlErrorsPerfDataCollectors[sqlInstance.name], err = perfdata.NewCollector(c.mssqlGetPerfObjectName(sqlInstance.name, "SQL Errors"), perfdata.InstancesAll, counters) if err != nil { - errs = append(errs, fmt.Errorf("failed to create SQL Errors collector for instance %s: %w", sqlInstance, err)) + errs = append(errs, fmt.Errorf("failed to create SQL Errors collector for instance %s: %w", sqlInstance.name, err)) } } diff --git a/internal/collector/mssql/mssql_sql_stats.go b/internal/collector/mssql/mssql_sql_stats.go index 78f0b84b4..f68234952 100644 --- a/internal/collector/mssql/mssql_sql_stats.go +++ b/internal/collector/mssql/mssql_sql_stats.go @@ -73,10 +73,10 @@ func (c *Collector) buildSQLStats() error { sqlStatsUnsafeAutoParamsPerSec, } - for sqlInstance := range c.mssqlInstances { - c.sqlStatsPerfDataCollectors[sqlInstance], err = perfdata.NewCollector(c.mssqlGetPerfObjectName(sqlInstance, "SQL Statistics"), nil, counters) + for _, sqlInstance := range c.mssqlInstances { + c.sqlStatsPerfDataCollectors[sqlInstance.name], err = perfdata.NewCollector(c.mssqlGetPerfObjectName(sqlInstance.name, "SQL Statistics"), nil, counters) if err != nil { - errs = append(errs, fmt.Errorf("failed to create SQL Statistics collector for instance %s: %w", sqlInstance, err)) + errs = append(errs, fmt.Errorf("failed to create SQL Statistics collector for instance %s: %w", sqlInstance.name, err)) } } diff --git a/internal/collector/mssql/mssql_transactions.go b/internal/collector/mssql/mssql_transactions.go index 11a99adc7..fc8ecea7e 100644 --- a/internal/collector/mssql/mssql_transactions.go +++ b/internal/collector/mssql/mssql_transactions.go @@ -79,10 +79,10 @@ func (c *Collector) buildTransactions() error { transactionsVersionStoreunittruncation, } - for sqlInstance := range c.mssqlInstances { - c.transactionsPerfDataCollectors[sqlInstance], err = perfdata.NewCollector(c.mssqlGetPerfObjectName(sqlInstance, "Transactions"), nil, counters) + for _, sqlInstance := range c.mssqlInstances { + c.transactionsPerfDataCollectors[sqlInstance.name], err = perfdata.NewCollector(c.mssqlGetPerfObjectName(sqlInstance.name, "Transactions"), nil, counters) if err != nil { - errs = append(errs, fmt.Errorf("failed to create Transactions collector for instance %s: %w", sqlInstance, err)) + errs = append(errs, fmt.Errorf("failed to create Transactions collector for instance %s: %w", sqlInstance.name, err)) } } diff --git a/internal/collector/mssql/mssql_wait_stats.go b/internal/collector/mssql/mssql_wait_stats.go index 2deacb3d7..720ac31f1 100644 --- a/internal/collector/mssql/mssql_wait_stats.go +++ b/internal/collector/mssql/mssql_wait_stats.go @@ -76,10 +76,10 @@ func (c *Collector) buildWaitStats() error { waitStatsTransactionOwnershipWaits, } - for sqlInstance := range c.mssqlInstances { - c.waitStatsPerfDataCollectors[sqlInstance], err = perfdata.NewCollector(c.mssqlGetPerfObjectName(sqlInstance, "Wait Statistics"), perfdata.InstancesAll, counters) + for _, sqlInstance := range c.mssqlInstances { + c.waitStatsPerfDataCollectors[sqlInstance.name], err = perfdata.NewCollector(c.mssqlGetPerfObjectName(sqlInstance.name, "Wait Statistics"), perfdata.InstancesAll, counters) if err != nil { - errs = append(errs, fmt.Errorf("failed to create Wait Statistics collector for instance %s: %w", sqlInstance, err)) + errs = append(errs, fmt.Errorf("failed to create Wait Statistics collector for instance %s: %w", sqlInstance.name, err)) } } diff --git a/internal/collector/mssql/types.go b/internal/collector/mssql/types.go new file mode 100644 index 000000000..9d14d8948 --- /dev/null +++ b/internal/collector/mssql/types.go @@ -0,0 +1,112 @@ +package mssql + +import ( + "fmt" + "strings" + + "golang.org/x/sys/windows/registry" +) + +type mssqlInstance struct { + name string + majorVersion mssqlServerMajorVersion + patchVersion string + edition string +} + +func newMssqlInstance(name string) (mssqlInstance, error) { + regKey := fmt.Sprintf(`Software\Microsoft\Microsoft SQL Server\%s\Setup`, name) + + k, err := registry.OpenKey(registry.LOCAL_MACHINE, regKey, registry.QUERY_VALUE) + if err != nil { + return mssqlInstance{}, fmt.Errorf("couldn't open registry Software\\Microsoft\\Microsoft SQL Server\\%s\\Setup: %w", name, err) + } + + defer func(key registry.Key) { + _ = key.Close() + }(k) + + patchVersion, _, err := k.GetStringValue("Version") + if err != nil { + return mssqlInstance{}, fmt.Errorf("couldn't get version from registry: %w", err) + } + + edition, _, err := k.GetStringValue("Edition") + if err != nil { + return mssqlInstance{}, fmt.Errorf("couldn't get version from registry: %w", err) + } + + _, name, _ = strings.Cut(name, ".") + + return mssqlInstance{ + edition: edition, + name: name, + majorVersion: newMajorVersion(patchVersion), + patchVersion: patchVersion, + }, nil +} + +func (m mssqlInstance) isGreaterOrEqualThan(version mssqlServerMajorVersion) bool { + return m.majorVersion.isGreaterOrEqualThan(version) +} + +type mssqlServerMajorVersion int + +const ( + // https://sqlserverbuilds.blogspot.com/ + serverVersionUnknown mssqlServerMajorVersion = 0 + serverVersion2012 mssqlServerMajorVersion = 11 + serverVersion2014 mssqlServerMajorVersion = 12 + serverVersion2016 mssqlServerMajorVersion = 13 + serverVersion2017 mssqlServerMajorVersion = 14 + serverVersion2019 mssqlServerMajorVersion = 15 + serverVersion2022 mssqlServerMajorVersion = 16 + serverVersion2025 mssqlServerMajorVersion = 17 +) + +func newMajorVersion(patchVersion string) mssqlServerMajorVersion { + majorVersion, _, _ := strings.Cut(patchVersion, ".") + switch majorVersion { + case "11": + return serverVersion2012 + case "12": + return serverVersion2014 + case "13": + return serverVersion2016 + case "14": + return serverVersion2017 + case "15": + return serverVersion2019 + case "16": + return serverVersion2022 + case "17": + return serverVersion2025 + default: + return serverVersionUnknown + } +} + +func (m mssqlServerMajorVersion) String() string { + switch m { + case serverVersion2012: + return "2012" + case serverVersion2014: + return "2014" + case serverVersion2016: + return "2016" + case serverVersion2017: + return "2017" + case serverVersion2019: + return "2019" + case serverVersion2022: + return "2022" + case serverVersion2025: + return "2025" + default: + return "unknown" + } +} + +func (m mssqlServerMajorVersion) isGreaterOrEqualThan(version mssqlServerMajorVersion) bool { + return m >= version +}