From 8c33aba631e29a1764ad36115fd5ef333a8bb02f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-Otto=20Kr=C3=B6pke?= Date: Thu, 15 Oct 2026 20:52:17 +0200 Subject: [PATCH] mssql: fix units and types of cumulative and current counters (#2723) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jan-Otto Kröpke --- UPGRADE.md | 37 +- docs/collector.mssql.md | 154 ++++---- internal/collector/mssql/AGENTS.md | 15 +- .../collector/mssql/counter_units_test.go | 341 ++++++++++++++++-- .../collector/mssql/mssql_access_methods.go | 6 +- .../collector/mssql/mssql_buffer_manager.go | 16 +- internal/collector/mssql/mssql_database.go | 60 +-- .../collector/mssql/mssql_database_replica.go | 46 +-- .../mssql/mssql_general_statistics.go | 52 +-- internal/collector/mssql/mssql_locks.go | 18 +- .../collector/mssql/mssql_transactions.go | 79 ++-- internal/collector/mssql/mssql_wait_stats.go | 37 +- 12 files changed, 634 insertions(+), 227 deletions(-) diff --git a/UPGRADE.md b/UPGRADE.md index cbde334a..00a07aca 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -17,9 +17,9 @@ Most installations need no changes. Go through the checklist below and read the | You use the `container` collector | Replace `containerd-state-dir` with `cri-endpoint` | [container collector](#container-collector) | | You query `windows_gpu_info` | Replace the `phys` label with `device_number` | [GPU collector](#gpu-collector) | | You alert on BitLocker status | Replace `status="disabled"` with `status="off"` | [BitLocker status](#bitlocker-status) | -| You query Active Directory latency or security descriptor propagation, DFSR, DNS dynamic update, Hyper-V, logical disk queue, cache, IIS, memory, RemoteFX, scheduled task, SMB server share or terminal services metrics | Update queries | [Renamed, deprecated and retyped metrics](#renamed-deprecated-and-retyped-metrics) | +| You query Active Directory latency or security descriptor propagation, DFSR, DNS dynamic update, Hyper-V, logical disk queue, cache, IIS, memory, RemoteFX, SQL Server, scheduled task, SMB server share or terminal services metrics | Update queries | [Renamed, deprecated and retyped metrics](#renamed-deprecated-and-retyped-metrics) | | You enable the `scheduled_task` collector and use tasks below `\Microsoft\` or missed runs | Set `--collector.scheduled_task.exclude` or `missed-runs` | [scheduled_task collector](#scheduled_task-collector) | -| You chart SQL Server latencies, SQL Server lock counts, DHCP denials, AD LSA writes, AD computer creations, AD FS database query times, DNS zone transfers or RemoteFX skipped frames | Expect corrected values | [Corrected values](#corrected-values) | +| You chart SQL Server latencies, lock counts or replica queues, DHCP denials, AD LSA writes, AD computer creations, AD FS database query times, DNS zone transfers or RemoteFX skipped frames | Expect corrected values | [Corrected values](#corrected-values) | | You enable the `hyperv` or `mscluster` collector | Check the new default sub-collectors | [New default sub-collectors](#new-default-sub-collectors) | | You set scrape timeouts above 5 minutes or below the timeout margin | Review the new limits | [Scrape timeouts](#scrape-timeouts) | | You use the sample Grafana dashboard | Use Grafana 13 or later and import the new dashboard | [Dashboard and alerts](#dashboard-and-alerts) | @@ -253,6 +253,28 @@ Renamed: | `windows_iis_http_requests_arrival_rate` | `windows_iis_http_requests_arrived_total` | Was a running count, now a counter, use `rate()`; label `app` | | `windows_iis_http_requests_max_queue_item_age` | `windows_iis_http_requests_max_queue_item_age_seconds` | Left out while the queue is empty; label `app` | | `windows_dns_dynamic_updates_queued` | `windows_dns_dynamic_updates_queued_total` | Now a counter, use `rate()` | +| `windows_mssql_databases_log_flush_wait_seconds` | `windows_mssql_databases_log_flush_wait_seconds_total` | Now a counter, use `rate()` | +| `windows_mssql_databases_log_flush_write_seconds` | `windows_mssql_databases_log_flush_write_seconds_total` | Now a counter, use `rate()` | +| `windows_mssql_databases_log_growths` | `windows_mssql_databases_log_growths_total` | Now a counter, use `increase()` | +| `windows_mssql_databases_log_shrinks` | `windows_mssql_databases_log_shrinks_total` | Now a counter, use `increase()` | +| `windows_mssql_databases_log_truncations` | `windows_mssql_databases_log_truncations_total` | Now a counter, use `increase()` | +| `windows_mssql_databases_xtp_controller_dlc_fetch_latency_seconds` | `windows_mssql_databases_xtp_controller_dlc_fetch_latency_seconds_total` | Now a counter in seconds; divide its `rate()` by that of the new `windows_mssql_databases_xtp_controller_dlc_fetches_total` | +| `windows_mssql_dbreplica_database_flow_control_wait_seconds` | `windows_mssql_dbreplica_database_flow_control_wait_seconds_total` | Now a counter, use `rate()` | +| `windows_mssql_dbreplica_group_commit_stall_seconds` | `windows_mssql_dbreplica_group_commit_stall_seconds_total` | Now a counter, use `rate()` | +| `windows_mssql_dbreplica_transaction_delay_seconds` | `windows_mssql_dbreplica_transaction_delay_seconds_total` | Now a counter, use `rate()` | +| `windows_mssql_dbreplica_recovery_queue_records` | `windows_mssql_dbreplica_recovery_queue_bytes` | Was kilobytes, now bytes | +| `windows_mssql_dbreplica_log_send_queue` | `windows_mssql_dbreplica_log_send_queue_bytes` | Was kilobytes, now bytes | +| `windows_mssql_dbreplica_log_remaining_for_undo` | `windows_mssql_dbreplica_log_remaining_for_undo_bytes` | Was kilobytes, now bytes | +| `windows_mssql_dbreplica_total_log_requiring_undo` | `windows_mssql_dbreplica_total_log_requiring_undo_bytes` | Was kilobytes, now bytes | +| `windows_mssql_genstats_http_authenticated_requests`, `windows_mssql_genstats_mars_deadlocks`, `windows_mssql_genstats_soap_empty_requests`, `windows_mssql_genstats_soap_method_invocations`, `windows_mssql_genstats_soap_session_initiate_requests`, `windows_mssql_genstats_soap_session_terminate_requests`, `windows_mssql_genstats_soapsql_requests`, `windows_mssql_genstats_soapwsdl_requests`, `windows_mssql_genstats_sql_trace_io_provider_lock_waits`, `windows_mssql_genstats_tempdb_recovery_unit_ids_generated`, `windows_mssql_genstats_tempdb_rowset_ids_generated` | The same names with a `_total` suffix | Now counters, use `rate()` | +| `windows_mssql_locks_wait_time_seconds` | `windows_mssql_locks_wait_time_seconds_total` | Now a counter, use `rate()` | +| `windows_mssql_locks_count` | `windows_mssql_locks_count_total` | Now a counter, use `rate()` | +| `windows_mssql_locks_lock_wait_seconds` | `windows_mssql_locks_lock_wait_seconds_total` | Now a counter, use `rate()` | +| `windows_mssql_transactions_nonsnapshot_version_active_total` | `windows_mssql_transactions_nonsnapshot_version_active` | Now a gauge, query it directly | +| `windows_mssql_transactions_snapshot_active_total` | `windows_mssql_transactions_snapshot_active` | Now a gauge, query it directly | +| `windows_mssql_transactions_update_snapshot_active_total` | `windows_mssql_transactions_update_snapshot_active` | Now a gauge, query it directly | +| `windows_mssql_transactions_version_cleanup_rate_bytes` | `windows_mssql_transactions_version_cleanup_bytes_total` | Now a counter, use `rate()` | +| `windows_mssql_transactions_version_generation_rate_bytes` | `windows_mssql_transactions_version_generation_bytes_total` | Now a counter, use `rate()` | The RemoteFX loss, FEC and retransmission gauges were not percentages. They exported the raw numerator of a Windows sample fraction, a running count that only grows. The numerator and its base are now two counters. Get the ratio with `rate(windows_remote_fx_net_lost_packets_total[5m]) / rate(windows_remote_fx_net_loss_sampled_packets_total[5m])`, and likewise with `windows_remote_fx_net_fec_packets_total` / `windows_remote_fx_net_fec_sampled_packets_total` and `windows_remote_fx_net_retransmitted_packets_total` / `windows_remote_fx_net_retransmission_sampled_packets_total`. @@ -297,10 +319,14 @@ Changed type, same name: | `windows_cache_copy_read_hits_total` | gauge | counter | | `windows_udp_datagram_received_total` | gauge | counter | | `windows_hyperv_legacy_network_adapter_bytes_dropped_total` | gauge | counter | +| `windows_mssql_waitstats_*` (all 12 wait types) | counter | gauge | +| `windows_mssql_transactions_version_store_units` | counter | gauge | The IIS and SMB values are current occupancy or, for `worker_max_threads`, the configured thread pool limit, so query them directly instead of with `rate()`. -If Prometheus scrapes with OpenMetrics, the IIS and SMB metrics were stored with a `_total` suffix, which counters get in that format. They now arrive without it. +If Prometheus scrapes with OpenMetrics, the IIS, SMB and SQL Server metrics above were stored with a `_total` suffix, which counters get in that format. They now arrive without it. The cache, UDP and Hyper-V metrics already end in `_total`, so only their type metadata changes; `rate()` queries keep working. +The SQL Server wait statistics hold values that SQL Server computes over its last sampling interval (the `item` label names the statistic), and the version store unit count is a current value. Query them directly instead of with `rate()`. +The SQL Server metrics that gained a `_total` suffix hold running totals since the instance started, which 0.31 published as gauges. Label changes on existing metrics: @@ -317,11 +343,12 @@ Retired: `windows_net_route_info` was documented but never exported. It was remo These metrics keep their names, but 0.31 reported wrong values. Graphs show a step at the upgrade. -- `windows_mssql_databases_xtp_controller_dlc_peak_latency_seconds`, `windows_mssql_dbreplica_database_flow_control_wait_seconds` and `windows_mssql_dbreplica_group_commit_stall_seconds` are now in seconds. SQL Server reports microseconds, which 0.31 did not convert correctly. -- `windows_mssql_locks_count` is no longer divided by 1000. - `windows_ad_directory_operations_total{operation="write",origin="local_security_authority"}` now counts directory writes from the LSA. In 0.31 it repeated the LSA search count. - `windows_ad_sam_computer_creation_requests_total` and `windows_ad_sam_computer_creation_successful_requests_total` were swapped in 0.31. Requests now count all attempts, successful requests only the successful ones. - `windows_adfs_db_artifact_query_time_seconds_total` and `windows_adfs_db_config_query_time_seconds_total` are now in seconds. 0.31 scaled the raw ticks by a fixed `1e-8`, so on a typical 10 MHz time base the values were 10 times too small. The new `windows_adfs_db_artifact_queries_total` and `windows_adfs_db_config_queries_total` count the queries, so you can compute the average query time. +- `windows_mssql_databases_xtp_controller_dlc_peak_latency_seconds`, `windows_mssql_bufman_read_ahead_issuing_seconds`, `windows_mssql_dbreplica_database_flow_control_wait_seconds_total` and `windows_mssql_dbreplica_group_commit_stall_seconds_total` (renamed, see above) are now in seconds. SQL Server reports microseconds, which 0.31 did not convert correctly. `windows_mssql_databases_xtp_controller_dlc_fetch_latency_seconds_total` is converted from microseconds as well. +- `windows_mssql_locks_count_total` (renamed from `windows_mssql_locks_count`) is no longer divided by 1000. +- `windows_mssql_dbreplica_redo_remaining_bytes` is now in bytes. 0.31 exported kilobytes. The other renamed `windows_mssql_dbreplica_*_bytes` metrics are converted from kilobytes too. - `windows_dhcp_denied_due_to_nonmatch_total` now counts denials due to a non-match. In 0.31 it repeated the match denial count. - `windows_remote_fx_gfx_frames_skipped_insufficient_resource_total` with `resource="client"` and `resource="server"` now count the matching frames. In 0.31 the two label values were swapped. - `windows_dns_zone_transfer_requests_sent_total{qtype="soa"}` now counts zone transfer SOA requests sent. In 0.31 it reported zone transfer requests received. diff --git a/docs/collector.mssql.md b/docs/collector.mssql.md index 73d1aa8e..5387a282 100644 --- a/docs/collector.mssql.md +++ b/docs/collector.mssql.md @@ -75,8 +75,8 @@ Comma-separated list of MSSQL WMI classes to use. Supported values are `accessme | `windows_mssql_availreplica_sends_to_replica` | Number of Always On messages sent to this availability replica per second | counter | `mssql_instance`, `replica` | | `windows_mssql_availreplica_sends_to_transport` | Actual number of Always On messages sent per second over the network to the remote availability replica | counter | `mssql_instance`, `replica` | | `windows_mssql_bufman_background_writer_pages` | Number of pages flushed to enforce the recovery interval settings | counter | `mssql_instance` | -| `windows_mssql_bufman_buffer_cache_hits` | Number of pages found in the buffer cache without having to read from disk. Numerator of the Buffercachehitratio counter | gauge | `mssql_instance` | -| `windows_mssql_bufman_buffer_cache_lookups` | Number of buffer cache lookups. Denominator (base) of the Buffercachehitratio counter | gauge | `mssql_instance` | +| `windows_mssql_bufman_buffer_cache_hits` | Number of pages found in the buffer cache without a disk read, in the most recent sampling interval of SQL Server (a few seconds). Numerator of the Buffer cache hit ratio counter | gauge | `mssql_instance` | +| `windows_mssql_bufman_buffer_cache_lookups` | Number of buffer cache lookups in the most recent sampling interval of SQL Server (a few seconds). Base of the Buffer cache hit ratio counter | gauge | `mssql_instance` | | `windows_mssql_bufman_checkpoint_pages` | Indicates the number of pages flushed to disk per second by a checkpoint or other operation that require all dirty pages to be flushed | counter | `mssql_instance` | | `windows_mssql_bufman_database_pages` | Indicates the number of pages in the buffer pool with database content | gauge | `mssql_instance` | | `windows_mssql_bufman_extension_allocated_pages` | Total number of non-free cache pages in the buffer pool extension file | gauge | `mssql_instance` | @@ -95,13 +95,13 @@ Comma-separated list of MSSQL WMI classes to use. Supported values are `accessme | `windows_mssql_bufman_page_reads` | Indicates the number of physical database page reads that are issued per second | counter | `mssql_instance` | | `windows_mssql_bufman_page_writes` | Indicates the number of physical database page writes that are issued per second | counter | `mssql_instance` | | `windows_mssql_bufman_read_ahead_pages` | Indicates the number of pages read per second in anticipation of use | counter | `mssql_instance` | -| `windows_mssql_bufman_read_ahead_issuing_seconds` | Time (microseconds) spent issuing readahead | counter | `mssql_instance` | +| `windows_mssql_bufman_read_ahead_issuing_seconds` | Total time spent issuing read-ahead, in seconds | counter | `mssql_instance` | | `windows_mssql_bufman_target_pages` | Ideal number of pages in the buffer pool | gauge | `mssql_instance` | -| `windows_mssql_dbreplica_database_flow_control_wait_seconds` | _Not yet documented_ | gauge | `mssql_instance`, `replica` | +| `windows_mssql_dbreplica_database_flow_control_wait_seconds_total` | Total time spent in database flow control wait, in seconds | counter | `mssql_instance`, `replica` | | `windows_mssql_dbreplica_database_initiated_flow_controls` | _Not yet documented_ | counter | `mssql_instance`, `replica` | | `windows_mssql_dbreplica_received_file_bytes` | _Not yet documented_ | counter | `mssql_instance`, `replica` | | `windows_mssql_dbreplica_group_commits` | _Not yet documented_ | counter | `mssql_instance`, `replica` | -| `windows_mssql_dbreplica_group_commit_stall_seconds` | _Not yet documented_ | gauge | `mssql_instance`, `replica` | +| `windows_mssql_dbreplica_group_commit_stall_seconds_total` | Total time transactions waited for group commit, in seconds | counter | `mssql_instance`, `replica` | | `windows_mssql_dbreplica_log_apply_pending_queue` | _Not yet documented_ | gauge | `mssql_instance`, `replica` | | `windows_mssql_dbreplica_log_apply_ready_queue` | _Not yet documented_ | gauge | `mssql_instance`, `replica` | | `windows_mssql_dbreplica_log_compressed_bytes` | _Not yet documented_ | counter | `mssql_instance`, `replica` | @@ -111,16 +111,16 @@ Comma-separated list of MSSQL WMI classes to use. Supported values are `accessme | `windows_mssql_dbreplica_log_compression_cachemisses` | _Not yet documented_ | counter | `mssql_instance`, `replica` | | `windows_mssql_dbreplica_log_compressions` | _Not yet documented_ | counter | `mssql_instance`, `replica` | | `windows_mssql_dbreplica_log_decompressions` | _Not yet documented_ | counter | `mssql_instance`, `replica` | -| `windows_mssql_dbreplica_log_remaining_for_undo` | The amount of log, in bytes, remaining to complete the undo phase | gauge | `mssql_instance`, `replica` | -| `windows_mssql_dbreplica_log_send_queue` | Amount of log records in the log files of the primary database, in kilobytes, that haven't been sent to the secondary replica | gauge | `mssql_instance`, `replica` | +| `windows_mssql_dbreplica_log_remaining_for_undo_bytes` | The amount of log, in bytes, remaining to complete the undo phase | gauge | `mssql_instance`, `replica` | +| `windows_mssql_dbreplica_log_send_queue_bytes` | Amount of log, in bytes, in the log files of the primary database that has not been sent to the secondary replica | gauge | `mssql_instance`, `replica` | | `windows_mssql_dbreplica_mirrored_write_transactions` | Number of transactions that were written to the primary database and then waited to commit until the log was sent to the secondary database, in the last second | counter | `mssql_instance`, `replica` | -| `windows_mssql_dbreplica_recovery_queue_records` | Amount of log records in the log files of the secondary replica that have not been redone | gauge | `mssql_instance`, `replica` | +| `windows_mssql_dbreplica_recovery_queue_bytes` | Amount of hardened log, in bytes, in the log files of the secondary replica that has not been redone | gauge | `mssql_instance`, `replica` | | `windows_mssql_dbreplica_redo_blocks` | Number of times the redo thread was blocked on locks held by readers of the database | counter | `mssql_instance`, `replica` | -| `windows_mssql_dbreplica_redo_remaining_bytes` | The amount of log, in kilobytes, remaining to be redone to finish the reverting phase | gauge | `mssql_instance`, `replica` | +| `windows_mssql_dbreplica_redo_remaining_bytes` | The amount of log, in bytes, remaining to be redone to finish the reverting phase | gauge | `mssql_instance`, `replica` | | `windows_mssql_dbreplica_redone_bytes` | Amount of log records redone on the secondary database in the last second | counter | `mssql_instance`, `replica` | | `windows_mssql_dbreplica_redones` | _Not yet documented_ | counter | `mssql_instance`, `replica` | -| `windows_mssql_dbreplica_total_log_requiring_undo` | Total kilobytes of log that must be undone | gauge | `mssql_instance`, `replica` | -| `windows_mssql_dbreplica_transaction_delay_seconds` | Delay in waiting for unterminated commit acknowledgment for all the current transactions | gauge | `mssql_instance`, `replica` | +| `windows_mssql_dbreplica_total_log_requiring_undo_bytes` | Total amount of log, in bytes, that must be undone | gauge | `mssql_instance`, `replica` | +| `windows_mssql_dbreplica_transaction_delay_seconds_total` | Total time transaction termination waited for commit acknowledgement, in seconds | counter | `mssql_instance`, `replica` | | `windows_mssql_databases_active_transactions` | Number of active transactions for the database | gauge | `mssql_instance`, `database` | | `windows_mssql_databases_active_parallel_redo_threads` | Number of active parallel redo threads | gauge | `mssql_instance`, `database` | | `windows_mssql_databases_backup_restore_operations` | Read/write throughput for backup and restore operations of a database per second | counter | `mssql_instance`, `database` | @@ -129,18 +129,18 @@ Comma-separated list of MSSQL WMI classes to use. Supported values are `accessme | `windows_mssql_databases_commit_table_entries` | he size (row count) of the in-memory portion of the commit table for the database | gauge | `mssql_instance`, `database` | | `windows_mssql_databases_data_files_size_bytes` | Cumulative size (in kilobytes) of all the data files in the database including any automatic growth. Monitoring this counter is useful, for example, for determining the correct size of tempdb | gauge | `mssql_instance`, `database` | | `windows_mssql_databases_dbcc_logical_scan_bytes` | Number of logical read scan bytes per second for database console commands (DBCC) | counter | `mssql_instance`, `database` | -| `windows_mssql_databases_group_commit_stall_seconds` | Group stall time (microseconds) per second | counter | `mssql_instance`, `database` | +| `windows_mssql_databases_group_commit_stall_seconds` | Total group commit stall time, in seconds | counter | `mssql_instance`, `database` | | `windows_mssql_databases_log_flushed_bytes` | Total number of log bytes flushed | counter | `mssql_instance`, `database` | -| `windows_mssql_databases_log_cache_hits` | Number of log cache reads satisfied from the log cache. Numerator of the LogCacheHitRatio counter | gauge | `mssql_instance`, `database` | -| `windows_mssql_databases_log_cache_lookups` | Number of log cache lookups. Denominator (base) of the LogCacheHitRatio counter | gauge | `mssql_instance`, `database` | +| `windows_mssql_databases_log_cache_hits` | Number of log cache reads satisfied from the log cache. Numerator of the Log Cache Hit Ratio counter | gauge | `mssql_instance`, `database` | +| `windows_mssql_databases_log_cache_lookups` | Number of log cache lookups. Base of the Log Cache Hit Ratio counter | gauge | `mssql_instance`, `database` | | `windows_mssql_databases_log_cache_reads` | Reads performed per second through the log manager cache | counter | `mssql_instance`, `database` | | `windows_mssql_databases_log_files_size_bytes` | Cumulative size (in kilobytes) of all the transaction log files in the database | gauge | `mssql_instance`, `database` | | `windows_mssql_databases_log_files_used_size_bytes` | The cumulative used size of all the log files in the database | gauge | `mssql_instance`, `database` | -| `windows_mssql_databases_log_flushes` | Total wait time (in milliseconds) to flush the log. On an Always On secondary database, this value indicates the wait time for log records to be hardened to disk | counter | `mssql_instance`, `database` | +| `windows_mssql_databases_log_flushes` | Number of log flushes | counter | `mssql_instance`, `database` | | `windows_mssql_databases_log_flush_waits` | Number of commits per second waiting for the log flush | counter | `mssql_instance`, `database` | -| `windows_mssql_databases_log_flush_wait_seconds` | Number of commits per second waiting for the log flush | gauge | `mssql_instance`, `database` | -| `windows_mssql_databases_log_flush_write_seconds` | Time in milliseconds for performing writes of log flushes that were completed in the last second | gauge | `mssql_instance`, `database` | -| `windows_mssql_databases_log_growths` | Total number of times the transaction log for the database has been expanded | gauge | `mssql_instance`, `database` | +| `windows_mssql_databases_log_flush_wait_seconds_total` | Total time commits waited for log flushes, in seconds | counter | `mssql_instance`, `database` | +| `windows_mssql_databases_log_flush_write_seconds_total` | Total time spent writing log flushes, in seconds | counter | `mssql_instance`, `database` | +| `windows_mssql_databases_log_growths_total` | Total number of times the transaction log for the database has been expanded | counter | `mssql_instance`, `database` | | `windows_mssql_databases_log_pool_cache_misses` | Number of requests for which the log block was not available in the log pool | counter | `mssql_instance`, `database` | | `windows_mssql_databases_log_pool_disk_reads` | Number of disk reads that the log pool issued to fetch log blocks | counter | `mssql_instance`, `database` | | `windows_mssql_databases_log_pool_hash_deletes` | Rate of raw hash entry deletes from the Log Pool | counter | `mssql_instance`, `database` | @@ -156,8 +156,8 @@ Comma-separated list of MSSQL WMI classes to use. Supported values are `accessme | `windows_mssql_databases_log_pool_requests` | The number of log-block requests processed by the log pool | counter | `mssql_instance`, `database` | | `windows_mssql_databases_log_pool_total_active_log_bytes` | Current total active log stored in the shared cache buffer manager in bytes | gauge | `mssql_instance`, `database` | | `windows_mssql_databases_log_pool_total_shared_pool_bytes` | Current total memory usage of the shared cache buffer manager in bytes | gauge | `mssql_instance`, `database` | -| `windows_mssql_databases_log_shrinks` | Total number of log shrinks for this database | gauge | `mssql_instance`, `database` | -| `windows_mssql_databases_log_truncations` | The number of times the transaction log has been truncated (in Simple Recovery Model) | gauge | `mssql_instance`, `database` | +| `windows_mssql_databases_log_shrinks_total` | Total number of log shrinks for this database | counter | `mssql_instance`, `database` | +| `windows_mssql_databases_log_truncations_total` | The number of times the transaction log has been truncated (in Simple Recovery Model) | counter | `mssql_instance`, `database` | | `windows_mssql_databases_log_used_percent` | Percentage of space in the log that is in use | gauge | `mssql_instance`, `database` | | `windows_mssql_databases_pending_repl_transactions` | Number of transactions in the transaction log of the publication database marked for replication, but not yet delivered to the distribution database | gauge | `mssql_instance`, `database` | | `windows_mssql_databases_repl_transactions` | Number of transactions per second read out of the transaction log of the publication database and delivered to the distribution database | counter | `mssql_instance`, `database` | @@ -165,42 +165,43 @@ Comma-separated list of MSSQL WMI classes to use. Supported values are `accessme | `windows_mssql_databases_tracked_transactions` | Number of committed transactions recorded in the commit table for the database | counter | `mssql_instance`, `database` | | `windows_mssql_databases_transactions` | Number of transactions started for the database per second | counter | `mssql_instance`, `database` | | `windows_mssql_databases_write_transactions` | Number of transactions that wrote to the database and committed, in the last second | counter | `mssql_instance`, `database` | -| `windows_mssql_databases_xtp_controller_dlc_fetch_latency_seconds` | Average latency in microseconds between log blocks entering the Direct Log Consumer and being retrieved by the XTP controller, per second | gauge | `mssql_instance`, `database` | -| `windows_mssql_databases_xtp_controller_dlc_peak_latency_seconds` | The largest recorded latency, in microseconds, of a fetch from the Direct Log Consumer by the XTP controller | gauge | `mssql_instance`, `database` | +| `windows_mssql_databases_xtp_controller_dlc_fetch_latency_seconds_total` | Total latency between log blocks entering the Direct Log Consumer and being retrieved by the XTP controller, in seconds. Numerator of the XTP Controller DLC Latency/Fetch counter | counter | `mssql_instance`, `database` | +| `windows_mssql_databases_xtp_controller_dlc_fetches_total` | Total number of fetches from the Direct Log Consumer by the XTP controller. Base of the XTP Controller DLC Latency/Fetch counter | counter | `mssql_instance`, `database` | +| `windows_mssql_databases_xtp_controller_dlc_peak_latency_seconds` | The largest recorded latency, in seconds, of a fetch from the Direct Log Consumer by the XTP controller | gauge | `mssql_instance`, `database` | | `windows_mssql_databases_xtp_controller_log_processed_bytes` | The amount of log bytes processed by the XTP controller thread, per second | counter | `mssql_instance`, `database` | | `windows_mssql_databases_xtp_memory_used_bytes` | The amount of memory used by XTP in the database | gauge | `mssql_instance`, `database` | | `windows_mssql_genstats_active_temp_tables` | Number of temporary tables/table variables in use | gauge | `mssql_instance` | | `windows_mssql_genstats_connection_resets` | Total number of logins started from the connection pool | counter | `mssql_instance` | | `windows_mssql_genstats_event_notifications_delayed_drop` | Number of event notifications waiting to be dropped by a system thread | gauge | `mssql_instance` | -| `windows_mssql_genstats_http_authenticated_requests` | Number of authenticated HTTP requests started per second | gauge | `mssql_instance` | +| `windows_mssql_genstats_http_authenticated_requests_total` | Total number of authenticated HTTP requests started | counter | `mssql_instance` | | `windows_mssql_genstats_logical_connections` | Number of logical connections to the system | gauge | `mssql_instance` | | `windows_mssql_genstats_logins` | Total number of logins started per second. This does not include pooled connections | counter | `mssql_instance` | | `windows_mssql_genstats_logouts` | Total number of logout operations started per second | counter | `mssql_instance` | -| `windows_mssql_genstats_mars_deadlocks` | Number of MARS deadlocks detected | gauge | `mssql_instance` | +| `windows_mssql_genstats_mars_deadlocks_total` | Total number of MARS deadlocks detected | counter | `mssql_instance` | | `windows_mssql_genstats_non_atomic_yields` | Number of non-atomic yields per second | counter | `mssql_instance` | | `windows_mssql_genstats_blocked_processes` | Number of currently blocked processes | gauge | `mssql_instance` | -| `windows_mssql_genstats_soap_empty_requests` | Number of empty SOAP requests started per second | gauge | `mssql_instance` | -| `windows_mssql_genstats_soap_method_invocations` | Number of SOAP method invocations started per second | gauge | `mssql_instance` | -| `windows_mssql_genstats_soap_session_initiate_requests` | Number of SOAP Session initiate requests started per second | gauge | `mssql_instance` | -| `windows_mssql_genstats_soap_session_terminate_requests` | Number of SOAP Session terminate requests started per second | gauge | `mssql_instance` | -| `windows_mssql_genstats_soapsql_requests` | Number of SOAP SQL requests started per second | gauge | `mssql_instance` | -| `windows_mssql_genstats_soapwsdl_requests` | Number of SOAP Web Service Description Language requests started per second | gauge | `mssql_instance` | -| `windows_mssql_genstats_sql_trace_io_provider_lock_waits` | Number of waits for the File IO Provider lock per second | gauge | `mssql_instance` | -| `windows_mssql_genstats_tempdb_recovery_unit_ids_generated` | Number of duplicate tempdb recovery unit ID generated | gauge | `mssql_instance` | -| `windows_mssql_genstats_tempdb_rowset_ids_generated` | Number of duplicate tempdb rowset ID generated | gauge | `mssql_instance` | +| `windows_mssql_genstats_soap_empty_requests_total` | Total number of empty SOAP requests started | counter | `mssql_instance` | +| `windows_mssql_genstats_soap_method_invocations_total` | Total number of SOAP method invocations started | counter | `mssql_instance` | +| `windows_mssql_genstats_soap_session_initiate_requests_total` | Total number of SOAP Session initiate requests started | counter | `mssql_instance` | +| `windows_mssql_genstats_soap_session_terminate_requests_total` | Total number of SOAP Session terminate requests started | counter | `mssql_instance` | +| `windows_mssql_genstats_soapsql_requests_total` | Total number of SOAP SQL requests started | counter | `mssql_instance` | +| `windows_mssql_genstats_soapwsdl_requests_total` | Total number of SOAP Web Service Description Language requests started | counter | `mssql_instance` | +| `windows_mssql_genstats_sql_trace_io_provider_lock_waits_total` | Total number of waits for the File IO Provider lock | counter | `mssql_instance` | +| `windows_mssql_genstats_tempdb_recovery_unit_ids_generated_total` | Total number of duplicate tempdb recovery unit IDs generated | counter | `mssql_instance` | +| `windows_mssql_genstats_tempdb_rowset_ids_generated_total` | Total number of duplicate tempdb rowset IDs generated | counter | `mssql_instance` | | `windows_mssql_genstats_temp_tables_creations` | Number of temporary tables/table variables created per second | counter | `mssql_instance` | | `windows_mssql_genstats_temp_tables_awaiting_destruction` | Number of temporary tables/table variables waiting to be destroyed by the cleanup system thread | gauge | `mssql_instance` | | `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` | A metric with a constant '1' value labeled with mssql instance information | gauge | `edition`, `mssql_instance`, `patch`, `version` | -| `windows_mssql_locks_wait_time_seconds` | Total time in seconds which locks have been holding resources. Numerator of the AverageWaitTimems counter | gauge | `mssql_instance`, `resource` | -| `windows_mssql_locks_count` | Count of how often requests have run into locks. Denominator (base) of the AverageWaitTimems counter | gauge | `mssql_instance`, `resource` | +| `windows_mssql_locks_wait_time_seconds_total` | Total time lock requests waited, in seconds. Numerator of the Average Wait Time (ms) counter | counter | `mssql_instance`, `resource` | +| `windows_mssql_locks_count_total` | Total number of lock requests that waited. Base of the Average Wait Time (ms) counter | counter | `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` | | `windows_mssql_locks_lock_timeouts_excluding_NOWAIT` | Number of lock requests per second that timed out, but excluding requests for NOWAIT locks | counter | `mssql_instance`, `resource` | -| `windows_mssql_locks_lock_waits` | Total wait time (in milliseconds) for locks in the last second | counter | `mssql_instance`, `resource` | -| `windows_mssql_locks_lock_wait_seconds` | Number of lock requests per second that required the caller to wait | gauge | `mssql_instance`, `resource` | +| `windows_mssql_locks_lock_waits` | Number of lock requests that required the caller to wait | counter | `mssql_instance`, `resource` | +| `windows_mssql_locks_lock_wait_seconds_total` | Total time lock requests waited, in seconds | counter | `mssql_instance`, `resource` | | `windows_mssql_locks_deadlocks` | Number of lock requests per second that resulted in a deadlock | counter | `mssql_instance`, `resource` | | `windows_mssql_memmgr_connection_memory_bytes` | Specifies the total amount of dynamic memory the server is using for maintaining connections | gauge | `mssql_instance` | | `windows_mssql_memmgr_database_cache_memory_bytes` | Specifies the amount of memory the server is currently using for the database pages cache | gauge | `mssql_instance` | @@ -234,31 +235,32 @@ Comma-separated list of MSSQL WMI classes to use. Supported values are `accessme | `windows_mssql_sqlstats_sql_recompilations` | Number of statement recompiles per second | counter | `mssql_instance` | | `windows_mssql_sqlstats_unsafe_auto_parameterization_attempts` | Number of unsafe auto-parameterization attempts per second. | counter | `mssql_instance` | | `windows_mssql_sql_errors_total` | Information for all errors | counter | `mssql_instance`, `resource` | -| `windows_mssql_transactions_tempdb_free_space_bytes` | The amount of space (in kilobytes) available in tempdb | gauge | `mssql_instance` | +| `windows_mssql_transactions_tempdb_free_space_bytes` | The amount of space, in bytes, available in tempdb | gauge | `mssql_instance` | | `windows_mssql_transactions_longest_transaction_running_seconds` | The length of time (in seconds) since the start of the transaction that has been active longer than any other current transaction | gauge | `mssql_instance` | -| `windows_mssql_transactions_nonsnapshot_version_active_total` | The number of currently active transactions that are not using snapshot isolation level and have made data modifications that have generated row versions in the tempdb version store | counter | `mssql_instance` | -| `windows_mssql_transactions_snapshot_active_total` | The number of currently active transactions using the snapshot isolation level | counter | `mssql_instance` | +| `windows_mssql_transactions_nonsnapshot_version_active` | The number of currently active transactions that are not using snapshot isolation level and have made data modifications that have generated row versions in the tempdb version store | gauge | `mssql_instance` | +| `windows_mssql_transactions_snapshot_active` | The number of currently active transactions using the snapshot isolation level | gauge | `mssql_instance` | | `windows_mssql_transactions_active` | The number of currently active transactions of all types | gauge | `mssql_instance` | -| `windows_mssql_transactions_update_conflicts_total` | The percentage of those transactions using the snapshot isolation level that have encountered update conflicts within the last second | counter | `mssql_instance` | -| `windows_mssql_transactions_update_snapshot_active_total` | The number of currently active transactions using the snapshot isolation level and have modified data | counter | `mssql_instance` | -| `windows_mssql_transactions_version_cleanup_rate_bytes` | The rate (in kilobytes per second) at which row versions are removed from the snapshot isolation version store in tempdb | gauge | `mssql_instance` | -| `windows_mssql_transactions_version_generation_rate_bytes` | The rate (in kilobytes per second) at which new row versions are added to the snapshot isolation version store in tempdb | gauge | `mssql_instance` | -| `windows_mssql_transactions_version_store_size_bytes` | he amount of space (in kilobytes) in tempdb being used to store snapshot isolation level row versions | gauge | `mssql_instance` | -| `windows_mssql_transactions_version_store_units` | The number of active allocation units in the snapshot isolation version store in tempdb | counter | `mssql_instance` | +| `windows_mssql_transactions_update_conflicts_total` | Total number of update snapshot transactions that had an update conflict. Numerator of the Update conflict ratio counter | counter | `mssql_instance` | +| `windows_mssql_transactions_update_snapshot_transactions_total` | Total number of update snapshot transactions. Base of the Update conflict ratio counter | counter | `mssql_instance` | +| `windows_mssql_transactions_update_snapshot_active` | The number of currently active transactions using the snapshot isolation level and have modified data | gauge | `mssql_instance` | +| `windows_mssql_transactions_version_cleanup_bytes_total` | Total size of row versions removed from the version store in tempdb, in bytes | counter | `mssql_instance` | +| `windows_mssql_transactions_version_generation_bytes_total` | Total size of row versions added to the version store in tempdb, in bytes | counter | `mssql_instance` | +| `windows_mssql_transactions_version_store_size_bytes` | The amount of space, in bytes, in tempdb used to store snapshot isolation level row versions | gauge | `mssql_instance` | +| `windows_mssql_transactions_version_store_units` | The number of active allocation units in the snapshot isolation version store in tempdb | gauge | `mssql_instance` | | `windows_mssql_transactions_version_store_creation_units` | The number of allocation units that have been created in the snapshot isolation store since the instance of the Database Engine was started | counter | `mssql_instance` | | `windows_mssql_transactions_version_store_truncation_units` | The number of allocation units that have been removed from the snapshot isolation store since the instance of the Database Engine was started | counter | `mssql_instance` | -| `windows_mssql_waitstats_lock_waits` | Statistics for processes waiting on a lock | counter | `mssql_instance`, `item` | -| `windows_mssql_waitstats_memory_grant_queue_waits` | Statistics for processes waiting for memory grant to become available | counter | `mssql_instance`, `item` | -| `windows_mssql_waitstats_thread_safe_memory_objects_waits` | Statistics for processes waiting on thread-safe memory allocators | counter | `mssql_instance`, `item` | -| `windows_mssql_waitstats_log_write_waits` | Statistics for processes waiting for log buffer to be written | counter | `mssql_instance`, `item` | -| `windows_mssql_waitstats_log_buffer_waits` | Statistics for processes waiting for log buffer to be available | counter | `mssql_instance`, `item` | -| `windows_mssql_waitstats_network_io_waits` | Statistics relevant to wait on network I/O | counter | `mssql_instance`, `item` | -| `windows_mssql_waitstats_page_io_latch_waits` | Statistics relevant to page I/O latches | counter | `mssql_instance`, `item` | -| `windows_mssql_waitstats_page_latch_waits` | Statistics relevant to page latches, not including I/O latches | counter | `mssql_instance`, `item` | -| `windows_mssql_waitstats_nonpage_latch_waits` | Statistics relevant to non-page latches | counter | `mssql_instance`, `item` | -| `windows_mssql_waitstats_wait_for_the_worker_waits` | Statistics relevant to processes waiting for worker to become available | counter | `mssql_instance`, `item` | -| `windows_mssql_waitstats_workspace_synchronization_waits` | Statistics relevant to processes synchronizing access to workspace | counter | `mssql_instance`, `item` | -| `windows_mssql_waitstats_transaction_ownership_waits` | Statistics relevant to processes synchronizing access to transaction | counter | `mssql_instance`, `item` | +| `windows_mssql_waitstats_lock_waits` | Statistics for processes waiting on a lock | gauge | `mssql_instance`, `item` | +| `windows_mssql_waitstats_memory_grant_queue_waits` | Statistics for processes waiting for memory grant to become available | gauge | `mssql_instance`, `item` | +| `windows_mssql_waitstats_thread_safe_memory_objects_waits` | Statistics for processes waiting on thread-safe memory allocators | gauge | `mssql_instance`, `item` | +| `windows_mssql_waitstats_log_write_waits` | Statistics for processes waiting for log buffer to be written | gauge | `mssql_instance`, `item` | +| `windows_mssql_waitstats_log_buffer_waits` | Statistics for processes waiting for log buffer to be available | gauge | `mssql_instance`, `item` | +| `windows_mssql_waitstats_network_io_waits` | Statistics relevant to wait on network I/O | gauge | `mssql_instance`, `item` | +| `windows_mssql_waitstats_page_io_latch_waits` | Statistics relevant to page I/O latches | gauge | `mssql_instance`, `item` | +| `windows_mssql_waitstats_page_latch_waits` | Statistics relevant to page latches, not including I/O latches | gauge | `mssql_instance`, `item` | +| `windows_mssql_waitstats_nonpage_latch_waits` | Statistics relevant to non-page latches | gauge | `mssql_instance`, `item` | +| `windows_mssql_waitstats_wait_for_the_worker_waits` | Statistics relevant to processes waiting for worker to become available | gauge | `mssql_instance`, `item` | +| `windows_mssql_waitstats_workspace_synchronization_waits` | Statistics relevant to processes synchronizing access to workspace | gauge | `mssql_instance`, `item` | +| `windows_mssql_waitstats_transaction_ownership_waits` | Statistics relevant to processes synchronizing access to transaction | gauge | `mssql_instance`, `item` | ### Example metric @@ -288,16 +290,32 @@ windows_mssql_bufman_buffer_cache_hits{instance="host:9182", mssql_instance="MSS windows_mssql_bufman_buffer_cache_lookups{instance="host:9182", mssql_instance="MSSQLSERVER"} ``` -This principal can be used for following metrics too: -- AccessMethodsWorktablesFromCacheHitRatio - - accessmethods_worktables_from_cache_hits - - accessmethods_worktables_from_cache_lookups -- LogCacheHitRatio - - databases_log_cache_hits - - databases_log_cache_lookups -- AverageLockWaitTime - - locks_wait_time_seconds - - locks_count +SQL Server refreshes both values every few seconds with the hits and lookups of the last interval, so they are gauges and need no `rate()`. +The same applies to `databases_log_cache_hits` and `databases_log_cache_lookups`. + +### Ratios and averages of running totals + +The Worktables From Cache Ratio, Average Wait Time (ms), XTP Controller DLC Latency/Fetch and Update conflict ratio counters are running totals. +Divide the rates of numerator and base: + +``` +# Worktables From Cache Ratio +rate(windows_mssql_accessmethods_worktables_from_cache_hits[5m]) / rate(windows_mssql_accessmethods_worktables_from_cache_lookups[5m]) + +# Average lock wait time in seconds +rate(windows_mssql_locks_wait_time_seconds_total[5m]) / rate(windows_mssql_locks_count_total[5m]) + +# Average XTP controller DLC latency per fetch in seconds +rate(windows_mssql_databases_xtp_controller_dlc_fetch_latency_seconds_total[5m]) / rate(windows_mssql_databases_xtp_controller_dlc_fetches_total[5m]) + +# Update conflict ratio +rate(windows_mssql_transactions_update_conflicts_total[5m]) / rate(windows_mssql_transactions_update_snapshot_transactions_total[5m]) +``` + +### Wait statistics + +Each `windows_mssql_waitstats_*` metric is a wait type. The `item` label names the statistic: `Average wait time (ms)`, `Cumulative wait time (ms) per second`, `Waits in progress` and `Waits started per second`. +SQL Server computes them over its most recent sampling interval, so they are gauges. Values of the `(ms)` items are in milliseconds. ## Alerting examples diff --git a/internal/collector/mssql/AGENTS.md b/internal/collector/mssql/AGENTS.md index e54aca72..dfdba77e 100644 --- a/internal/collector/mssql/AGENTS.md +++ b/internal/collector/mssql/AGENTS.md @@ -3,7 +3,10 @@ The collector reads the SQL Server performance objects (`Access Methods`, `Buffer Manager`, `Databases`, `Locks`, `Wait Statistics` and others) with `pdh.CounterTypeRaw`, one PDH collector per SQL Server instance. It needs a -SQL Server Database Engine; CI does not provision one. These notes were +SQL Server Database Engine. CI provisions SQL Server Express (instance +`CISQL`, database `CIWindowsExporter`) and lists `mssql` in +`WINDOWS_EXPORTER_TEST_COLLECTORS`, so live tests must fail there instead of +skipping. Express has no availability groups. These notes were recorded on Windows Server 2025 (build 26100) with SQL Server 2022 Developer Edition (`PatchLevel` 16.0.1000.6), named instance `NONDEFAULT`. @@ -35,7 +38,15 @@ Edition (`PatchLevel` 16.0.1000.6), named instance `NONDEFAULT`. `Cumulative wait time (ms) per second`, `Waits in progress` and `Waits started per second`. All counters are `PERF_COUNTER_LARGE_RAWCOUNT`, so the meaning comes from the instance name. - All values stayed 0 over 10 seconds on this idle instance. + SQL Server recomputes every item about every 5 seconds over its last + interval. Under lock contention, `Waits started per second` showed 5 and + then 0 while `sys.dm_os_wait_stats` kept growing, so all items are gauges. +- `Buffer cache hit ratio` (`PERF_RAW_FRACTION`) is windowed as well: value + and base are the page lookups of the last refresh, so it is a gauge. + `Worktables From Cache Ratio` has a growing base and is a running total. +- `TestMetricTypesMatchCounterTypes` compares the published types with + `pdh.Collector.MetricType` against a live instance; deliberate exceptions + (`Log Growths`, `Log Shrinks`, `Log Truncations`) are listed there. - Every `.../sec` counter, and several counters without `/sec` (for example `Mars Deadlocks`, the `SOAP ...` and `Lob...Count` counters, `Log Flush Wait Time`, `Group Commit Time`, `Transaction Delay`, diff --git a/internal/collector/mssql/counter_units_test.go b/internal/collector/mssql/counter_units_test.go index 20597e7e..b22b5ac0 100644 --- a/internal/collector/mssql/counter_units_test.go +++ b/internal/collector/mssql/counter_units_test.go @@ -18,51 +18,346 @@ package mssql import ( + "log/slog" + "os" + "slices" + "strings" "testing" "github.com/prometheus/client_golang/prometheus" dto "github.com/prometheus/client_model/go" "github.com/stretchr/testify/require" + "golang.org/x/sys/windows/registry" ) +// emitCollector is an unchecked collector that publishes the metrics of emit. +type emitCollector func(ch chan<- prometheus.Metric) + +func (emitCollector) Describe(chan<- *prometheus.Desc) {} + +func (e emitCollector) Collect(ch chan<- prometheus.Metric) { e(ch) } + +type wantMetric struct { + metricType dto.MetricType + value float64 +} + +func gatherFirstSeries(t *testing.T, collector prometheus.Collector) map[string]*dto.Metric { + t.Helper() + + registry := prometheus.NewPedanticRegistry() + require.NoError(t, registry.Register(collector)) + + families, err := registry.Gather() + require.NoError(t, err) + + series := make(map[string]*dto.Metric, len(families)) + + for _, family := range families { + require.NotEmpty(t, family.GetMetric()) + + series[family.GetName()] = family.GetMetric()[0] + } + + return series +} + +func metricValue(m *dto.Metric) (dto.MetricType, float64) { + if m.GetCounter() != nil { + return dto.MetricType_COUNTER, m.GetCounter().GetValue() + } + + return dto.MetricType_GAUGE, m.GetGauge().GetValue() +} + func TestCounterUnitConversions(t *testing.T) { t.Parallel() c := New(nil) + c.logger = slog.New(slog.DiscardHandler) // No SQL instances are needed to build the metric descriptors. + require.NoError(t, c.buildBufferManager()) require.NoError(t, c.buildDatabases()) require.NoError(t, c.buildDatabaseReplica()) + require.NoError(t, c.buildGeneralStatistics()) require.NoError(t, c.buildLocks()) - c.databasesPerfDataObject = []perfDataCounterValuesDatabases{{Name: "db", DatabasesXTPControllerDLCPeakLatency: 1000}} - c.dbReplicaPerfDataObject = []perfDataCounterValuesDBReplica{{Name: "replica", DbReplicaDatabaseFlowControlDelay: 1000, DbReplicaGroupCommitTime: 1000}} - c.locksPerfDataObject = []perfDataCounterValuesLocks{{Name: "locks", LocksAverageWaitTimeMSBase: 1000}} + require.NoError(t, c.buildTransactions()) + require.NoError(t, c.buildWaitStats()) + + c.bufManPerfDataObject = []perfDataCounterValuesBufMan{{BufManReadaheadTimePerSec: 882}} + c.databasesPerfDataObject = []perfDataCounterValuesDatabases{{ + Name: "db", + DatabasesLogFlushWaitTime: 4, + DatabasesLogFlushWriteTimeMS: 3, + DatabasesLogGrowths: 2, + DatabasesLogShrinks: 1, + DatabasesLogTruncations: 5, + DatabasesXTPControllerDLCLatencyPerFetch: 2500, + DatabasesXTPControllerDLCLatencyPerFetchBase: 5, + DatabasesXTPControllerDLCPeakLatency: 1000, + }} + c.dbReplicaPerfDataObject = []perfDataCounterValuesDBReplica{{ + Name: "replica", + DbReplicaDatabaseFlowControlDelay: 1000, + DbReplicaGroupCommitTime: 1000, + DbReplicaTransactionDelay: 1500, + DbReplicaRecoveryQueue: 2, + DbReplicaRedoBytesRemaining: 3, + DbReplicaLogSendQueue: 4, + DbReplicaTotalLogRequiringUndo: 5, + DbReplicaLogRemainingForUndo: 6, + }} + c.genStatsPerfDataObject = []perfDataCounterValuesGenStats{{ + GenStatsHTTPAuthenticatedRequests: 1, + GenStatsMarsDeadlocks: 2, + GenStatsSOAPEmptyRequests: 3, + GenStatsSOAPMethodInvocations: 4, + GenStatsSOAPSessionInitiateRequests: 5, + GenStatsSOAPSessionTerminateRequests: 6, + GenStatsSOAPSQLRequests: 7, + GenStatsSOAPWSDLRequests: 8, + GenStatsSQLTraceIOProviderLockWaits: 9, + GenStatsTempdbRecoveryUnitID: 10, + GenStatsTempdbRowsetID: 11, + }} + c.locksPerfDataObject = []perfDataCounterValuesLocks{{ + Name: "locks", + LocksAverageWaitTimeMS: 499, + LocksAverageWaitTimeMSBase: 3, + LocksLockWaitTimeMS: 499, + }} + c.transactionsPerfDataObject = []perfDataCounterValuesTransactions{{ + TransactionsNonSnapshotVersionTransactions: 1, + TransactionsSnapshotTransactions: 2, + TransactionsUpdateSnapshotTransactions: 3, + TransactionsUpdateconflictratio: 4, + TransactionsUpdateconflictratioBase: 8, + TransactionsVersionCleanuprateKBPers: 5, + TransactionsVersionGenerationrateKBPers: 6, + TransactionsVersionStoreunitcount: 7, + }} + c.waitStatsPerfDataObject = []perfDataCounterValuesWaitStats{{ + Name: "Waits started per second", + WaitStatsNetworkIOWaits: 2194, + }} + instance := mssqlInstance{name: "test"} - metrics := make(chan prometheus.Metric, 200) - c.emitDatabasesMetrics(metrics, instance) - c.emitDatabaseReplicaMetrics(metrics, instance) - c.emitLocksMetrics(metrics, instance) - close(metrics) + series := gatherFirstSeries(t, emitCollector(func(ch chan<- prometheus.Metric) { + c.emitBufferManagerMetrics(ch, instance) + c.emitDatabasesMetrics(ch, instance) + c.emitDatabaseReplicaMetrics(ch, instance) + c.emitGeneralStatisticsMetrics(ch, instance) + c.emitLocksMetrics(ch, instance) + c.emitTransactionsMetrics(ch, instance) + c.emitWaitStatsMetrics(ch, instance) + })) - want := map[*prometheus.Desc]float64{ - c.databasesXTPControllerDLCPeakLatency: 0.001, - c.dbReplicaDatabaseFlowControlDelay: 0.001, - c.dbReplicaGroupCommitTime: 0.001, - c.locksCount: 1000, + counter := dto.MetricType_COUNTER + gauge := dto.MetricType_GAUGE + + want := map[string]wantMetric{ + "windows_mssql_bufman_read_ahead_issuing_seconds": {counter, 882e-6}, + + "windows_mssql_databases_log_flush_wait_seconds_total": {counter, 0.004}, + "windows_mssql_databases_log_flush_write_seconds_total": {counter, 0.003}, + "windows_mssql_databases_log_growths_total": {counter, 2}, + "windows_mssql_databases_log_shrinks_total": {counter, 1}, + "windows_mssql_databases_log_truncations_total": {counter, 5}, + "windows_mssql_databases_xtp_controller_dlc_fetch_latency_seconds_total": {counter, 0.0025}, + "windows_mssql_databases_xtp_controller_dlc_fetches_total": {counter, 5}, + "windows_mssql_databases_xtp_controller_dlc_peak_latency_seconds": {gauge, 0.001}, + + "windows_mssql_dbreplica_database_flow_control_wait_seconds_total": {counter, 0.001}, + "windows_mssql_dbreplica_group_commit_stall_seconds_total": {counter, 0.001}, + "windows_mssql_dbreplica_transaction_delay_seconds_total": {counter, 1.5}, + "windows_mssql_dbreplica_recovery_queue_bytes": {gauge, 2048}, + "windows_mssql_dbreplica_redo_remaining_bytes": {gauge, 3072}, + "windows_mssql_dbreplica_log_send_queue_bytes": {gauge, 4096}, + "windows_mssql_dbreplica_total_log_requiring_undo_bytes": {gauge, 5120}, + "windows_mssql_dbreplica_log_remaining_for_undo_bytes": {gauge, 6144}, + + "windows_mssql_genstats_http_authenticated_requests_total": {counter, 1}, + "windows_mssql_genstats_mars_deadlocks_total": {counter, 2}, + "windows_mssql_genstats_soap_empty_requests_total": {counter, 3}, + "windows_mssql_genstats_soap_method_invocations_total": {counter, 4}, + "windows_mssql_genstats_soap_session_initiate_requests_total": {counter, 5}, + "windows_mssql_genstats_soap_session_terminate_requests_total": {counter, 6}, + "windows_mssql_genstats_soapsql_requests_total": {counter, 7}, + "windows_mssql_genstats_soapwsdl_requests_total": {counter, 8}, + "windows_mssql_genstats_sql_trace_io_provider_lock_waits_total": {counter, 9}, + "windows_mssql_genstats_tempdb_recovery_unit_ids_generated_total": {counter, 10}, + "windows_mssql_genstats_tempdb_rowset_ids_generated_total": {counter, 11}, + + "windows_mssql_locks_wait_time_seconds_total": {counter, 0.499}, + "windows_mssql_locks_count_total": {counter, 3}, + "windows_mssql_locks_lock_wait_seconds_total": {counter, 0.499}, + + "windows_mssql_transactions_nonsnapshot_version_active": {gauge, 1}, + "windows_mssql_transactions_snapshot_active": {gauge, 2}, + "windows_mssql_transactions_update_snapshot_active": {gauge, 3}, + "windows_mssql_transactions_update_conflicts_total": {counter, 4}, + "windows_mssql_transactions_update_snapshot_transactions_total": {counter, 8}, + "windows_mssql_transactions_version_cleanup_bytes_total": {counter, 5120}, + "windows_mssql_transactions_version_generation_bytes_total": {counter, 6144}, + "windows_mssql_transactions_version_store_units": {gauge, 7}, + + "windows_mssql_waitstats_network_io_waits": {gauge, 2194}, + "windows_mssql_waitstats_lock_waits": {gauge, 0}, } - found := make(map[*prometheus.Desc]bool) - for metric := range metrics { - expected, ok := want[metric.Desc()] - if !ok { + for name, expected := range want { + metric, ok := series[name] + require.True(t, ok, "metric %s not emitted", name) + + metricType, value := metricValue(metric) + require.Equal(t, expected.metricType, metricType, name) + require.InDelta(t, expected.value, value, 1e-9, name) + } + + for _, removed := range []string{ + "windows_mssql_databases_log_flush_wait_seconds", + "windows_mssql_databases_log_growths", + "windows_mssql_databases_xtp_controller_dlc_fetch_latency_seconds", + "windows_mssql_dbreplica_recovery_queue_records", + "windows_mssql_dbreplica_log_send_queue", + "windows_mssql_locks_count", + "windows_mssql_transactions_snapshot_active_total", + "windows_mssql_transactions_version_cleanup_rate_bytes", + } { + require.NotContains(t, series, removed) + } +} + +// TestMetricTypesMatchCounterTypes checks the published types of the metrics +// against the PDH counter types of a local SQL Server instance. +func TestMetricTypesMatchCounterTypes(t *testing.T) { + t.Parallel() + + // Collectors provisioned by CI must not be skipped, see docs/ci-testing.md. + required := slices.Contains(strings.Split(os.Getenv("WINDOWS_EXPORTER_TEST_COLLECTORS"), ","), Name) + + key, err := registry.OpenKey(registry.LOCAL_MACHINE, `Software\Microsoft\Microsoft SQL Server\Instance Names\SQL`, registry.QUERY_VALUE) + if err != nil { + require.False(t, required, "mssql is required but no SQL Server instance was found: %v", err) + t.Skipf("no SQL Server instance: %v", err) + } + + _ = key.Close() + + c := New(nil) + + t.Cleanup(func() { require.NoError(t, c.Close()) }) + + require.NoError(t, c.Build(slog.New(slog.DiscardHandler), nil)) + + if len(c.mssqlInstances) == 0 { + require.False(t, required, "mssql is required but no SQL Server instance was discovered") + t.Skip("no SQL Server instance") + } + + instance := c.mssqlInstances[0] + + type check struct { + metric string + counter string + // cumulative marks PERF_COUNTER_LARGE_RAWCOUNT counters whose help text + // says they are running totals, which PDH reports as gauges. + cumulative bool + metricType func(counter string) (prometheus.ValueType, bool) + } + + bufMan := c.bufManPerfDataCollectors[instance].MetricType + databases := c.databasesPerfDataCollectors[instance].MetricType + dbReplica := c.dbReplicaPerfDataCollectors[instance].MetricType + genStats := c.genStatsPerfDataCollectors[instance].MetricType + locks := c.locksPerfDataCollectors[instance].MetricType + transactions := c.transactionsPerfDataCollectors[instance].MetricType + waitStats := c.waitStatsPerfDataCollectors[instance].MetricType + + checks := []check{ + {"windows_mssql_bufman_read_ahead_issuing_seconds", "Readahead time/sec", false, bufMan}, + {"windows_mssql_databases_log_flush_wait_seconds_total", "Log Flush Wait Time", false, databases}, + {"windows_mssql_databases_log_flush_write_seconds_total", "Log Flush Write Time (ms)", false, databases}, + {"windows_mssql_databases_log_growths_total", "Log Growths", true, databases}, + {"windows_mssql_databases_log_shrinks_total", "Log Shrinks", true, databases}, + {"windows_mssql_databases_log_truncations_total", "Log Truncations", true, databases}, + {"windows_mssql_dbreplica_database_flow_control_wait_seconds_total", "Database Flow Control Delay", false, dbReplica}, + {"windows_mssql_dbreplica_group_commit_stall_seconds_total", "Group Commit Time", false, dbReplica}, + {"windows_mssql_dbreplica_transaction_delay_seconds_total", "Transaction Delay", false, dbReplica}, + {"windows_mssql_dbreplica_recovery_queue_bytes", "Recovery Queue", false, dbReplica}, + {"windows_mssql_dbreplica_redo_remaining_bytes", "Redo Bytes Remaining", false, dbReplica}, + {"windows_mssql_genstats_http_authenticated_requests_total", "HTTP Authenticated Requests", false, genStats}, + {"windows_mssql_genstats_mars_deadlocks_total", "Mars Deadlocks", false, genStats}, + {"windows_mssql_genstats_soap_empty_requests_total", "SOAP Empty Requests", false, genStats}, + {"windows_mssql_genstats_soap_method_invocations_total", "SOAP Method Invocations", false, genStats}, + {"windows_mssql_genstats_soap_session_initiate_requests_total", "SOAP Session Initiate Requests", false, genStats}, + {"windows_mssql_genstats_soap_session_terminate_requests_total", "SOAP Session Terminate Requests", false, genStats}, + {"windows_mssql_genstats_soapsql_requests_total", "SOAP SQL Requests", false, genStats}, + {"windows_mssql_genstats_soapwsdl_requests_total", "SOAP WSDL Requests", false, genStats}, + {"windows_mssql_genstats_sql_trace_io_provider_lock_waits_total", "SQL Trace IO Provider Lock Waits", false, genStats}, + {"windows_mssql_genstats_tempdb_recovery_unit_ids_generated_total", "Tempdb recovery unit id", false, genStats}, + {"windows_mssql_genstats_tempdb_rowset_ids_generated_total", "Tempdb rowset id", false, genStats}, + {"windows_mssql_locks_lock_wait_seconds_total", "Lock Wait Time (ms)", false, locks}, + {"windows_mssql_transactions_nonsnapshot_version_active", "NonSnapshot Version Transactions", false, transactions}, + {"windows_mssql_transactions_snapshot_active", "Snapshot Transactions", false, transactions}, + {"windows_mssql_transactions_update_snapshot_active", "Update Snapshot Transactions", false, transactions}, + {"windows_mssql_transactions_version_cleanup_bytes_total", "Version Cleanup rate (KB/s)", false, transactions}, + {"windows_mssql_transactions_version_generation_bytes_total", "Version Generation rate (KB/s)", false, transactions}, + {"windows_mssql_transactions_version_store_units", "Version Store unit count", false, transactions}, + {"windows_mssql_waitstats_lock_waits", "Lock waits", false, waitStats}, + {"windows_mssql_waitstats_network_io_waits", "Network IO waits", false, waitStats}, + } + + metrics := make(chan prometheus.Metric) + collected := make([]prometheus.Metric, 0, 1024) + done := make(chan struct{}) + + go func() { + defer close(done) + + for metric := range metrics { + collected = append(collected, metric) + } + }() + + collectErr := c.Collect(metrics, 0) + + close(metrics) + <-done + require.NoError(t, collectErr) + + series := gatherFirstSeries(t, emitCollector(func(ch chan<- prometheus.Metric) { + for _, metric := range collected { + ch <- metric + } + })) + + for _, check := range checks { + pdhType, ok := check.metricType(check.counter) + require.True(t, ok, "counter %s not found", check.counter) + + if check.cumulative { + require.Equal(t, prometheus.GaugeValue, pdhType, check.counter) + + pdhType = prometheus.CounterValue + } + + want := dto.MetricType_GAUGE + if pdhType == prometheus.CounterValue { + want = dto.MetricType_COUNTER + } + + metric, ok := series[check.metric] + if !ok && strings.HasPrefix(check.metric, "windows_mssql_dbreplica_") { + // Database Replica has only _Total without availability groups. + t.Logf("metric %s not emitted on this instance", check.metric) + continue } - var value dto.Metric - require.NoError(t, metric.Write(&value)) - require.InDelta(t, expected, value.GetGauge().GetValue(), 1e-9) + require.True(t, ok, "metric %s not emitted", check.metric) - found[metric.Desc()] = true + metricType, _ := metricValue(metric) + require.Equal(t, want, metricType, check.metric) } - - require.Len(t, found, len(want)) } diff --git a/internal/collector/mssql/mssql_access_methods.go b/internal/collector/mssql/mssql_access_methods.go index a89f62e2..b4d6c60e 100644 --- a/internal/collector/mssql/mssql_access_methods.go +++ b/internal/collector/mssql/mssql_access_methods.go @@ -119,8 +119,10 @@ type perfDataCounterValuesAccessMethods struct { AccessMethodsUsedTreePageCookie float64 `perfdata:"Used tree page cookie"` AccessMethodsWorkfilesCreatedPerSec float64 `perfdata:"Workfiles Created/sec"` AccessMethodsWorktablesCreatedPerSec float64 `perfdata:"Worktables Created/sec"` - AccessMethodsWorktablesFromCacheRatio float64 `perfdata:"Worktables From Cache Ratio"` - AccessMethodsWorktablesFromCacheRatioBase float64 `perfdata:"Worktables From Cache Ratio,secondvalue"` + // PERF_LARGE_RAW_FRACTION, but SQL Server keeps both values as running totals since + // the instance started, unlike Buffer cache hit ratio. Published as counters. + AccessMethodsWorktablesFromCacheRatio float64 `perfdata:"Worktables From Cache Ratio"` + AccessMethodsWorktablesFromCacheRatioBase float64 `perfdata:"Worktables From Cache Ratio,secondvalue"` } func (c *Collector) buildAccessMethods() error { diff --git a/internal/collector/mssql/mssql_buffer_manager.go b/internal/collector/mssql/mssql_buffer_manager.go index 1cad88a9..f4d503f6 100644 --- a/internal/collector/mssql/mssql_buffer_manager.go +++ b/internal/collector/mssql/mssql_buffer_manager.go @@ -56,7 +56,9 @@ type collectorBufferManager struct { } type perfDataCounterValuesBufMan struct { - BufManBackgroundWriterPagesPerSec float64 `perfdata:"Background writer pages/sec"` + BufManBackgroundWriterPagesPerSec float64 `perfdata:"Background writer pages/sec"` + // PERF_LARGE_RAW_FRACTION. SQL Server refreshes value and base every few seconds with the + // hits and lookups of the last interval, so both are gauges. BufManBufferCacheHitRatio float64 `perfdata:"Buffer cache hit ratio"` BufManBufferCacheHitRatioBase float64 `perfdata:"Buffer cache hit ratio,secondvalue"` BufManCheckpointPagesPerSec float64 `perfdata:"Checkpoint pages/sec"` @@ -222,7 +224,7 @@ func (c *Collector) buildBufferManager() error { ) c.bufManReadaheadtime = prometheus.NewDesc( prometheus.BuildFQName(types.Namespace, Name, "bufman_read_ahead_issuing_seconds"), - "(BufferManager.Readaheadtime)", + "(BufferManager.Readaheadtime) Total time spent issuing read-ahead, in seconds", []string{"mssql_instance"}, nil, ) @@ -246,6 +248,12 @@ func (c *Collector) collectBufferManagerInstance(ch chan<- prometheus.Metric, sq return fmt.Errorf("failed to collect %s metrics: %w", c.mssqlGetPerfObjectName(sqlInstance, "Buffer Manager"), err) } + c.emitBufferManagerMetrics(ch, sqlInstance) + + return nil +} + +func (c *Collector) emitBufferManagerMetrics(ch chan<- prometheus.Metric, sqlInstance mssqlInstance) { for _, data := range c.bufManPerfDataObject { ch <- prometheus.MustNewConstMetric( c.bufManBackgroundwriterpages, @@ -397,7 +405,7 @@ func (c *Collector) collectBufferManagerInstance(ch chan<- prometheus.Metric, sq ch <- prometheus.MustNewConstMetric( c.bufManReadaheadtime, prometheus.CounterValue, - data.BufManReadaheadTimePerSec, + data.BufManReadaheadTimePerSec/1e6, // microseconds sqlInstance.name, ) @@ -408,8 +416,6 @@ func (c *Collector) collectBufferManagerInstance(ch chan<- prometheus.Metric, sq sqlInstance.name, ) } - - return nil } func (c *Collector) closeBufferManager() { diff --git a/internal/collector/mssql/mssql_database.go b/internal/collector/mssql/mssql_database.go index 85b3153a..623d67fa 100644 --- a/internal/collector/mssql/mssql_database.go +++ b/internal/collector/mssql/mssql_database.go @@ -77,6 +77,7 @@ type collectorDatabases struct { databasesTransactions *prometheus.Desc databasesWriteTransactions *prometheus.Desc databasesXTPControllerDLCLatencyPerFetch *prometheus.Desc + databasesXTPControllerDLCFetches *prometheus.Desc databasesXTPControllerDLCPeakLatency *prometheus.Desc databasesXTPControllerLogProcessed *prometheus.Desc databasesXTPMemoryUsedKB *prometheus.Desc @@ -129,9 +130,11 @@ type perfDataCounterValuesDatabases struct { DatabasesTransactionsPerSec float64 `perfdata:"Transactions/sec"` DatabasesWriteTransactionsPerSec float64 `perfdata:"Write Transactions/sec"` DatabasesXTPControllerDLCLatencyPerFetch float64 `perfdata:"XTP Controller DLC Latency/Fetch"` - DatabasesXTPControllerDLCPeakLatency float64 `perfdata:"XTP Controller DLC Peak Latency"` - DatabasesXTPControllerLogProcessedPerSec float64 `perfdata:"XTP Controller Log Processed/sec"` - DatabasesXTPMemoryUsedKB float64 `perfdata:"XTP Memory Used (KB)"` + // PERF_AVERAGE_BULK: the base is the number of fetches. + DatabasesXTPControllerDLCLatencyPerFetchBase float64 `perfdata:"XTP Controller DLC Latency/Fetch,secondvalue"` + DatabasesXTPControllerDLCPeakLatency float64 `perfdata:"XTP Controller DLC Peak Latency"` + DatabasesXTPControllerLogProcessedPerSec float64 `perfdata:"XTP Controller Log Processed/sec"` + DatabasesXTPMemoryUsedKB float64 `perfdata:"XTP Memory Used (KB)"` } type perfDataCounterValuesDatabases2019 struct { @@ -264,20 +267,20 @@ func (c *Collector) buildDatabases() error { nil, ) c.databasesLogFlushWaitTime = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "databases_log_flush_wait_seconds"), - "(Databases.LogFlushWaitTime)", + prometheus.BuildFQName(types.Namespace, Name, "databases_log_flush_wait_seconds_total"), + "(Databases.LogFlushWaitTime) Total time commits waited for log flushes, in seconds", []string{"mssql_instance", "database"}, nil, ) c.databasesLogFlushWriteTimeMS = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "databases_log_flush_write_seconds"), - "(Databases.LogFlushWriteTimems)", + prometheus.BuildFQName(types.Namespace, Name, "databases_log_flush_write_seconds_total"), + "(Databases.LogFlushWriteTimems) Total time spent writing log flushes, in seconds", []string{"mssql_instance", "database"}, nil, ) c.databasesLogGrowths = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "databases_log_growths"), - "(Databases.LogGrowths)", + prometheus.BuildFQName(types.Namespace, Name, "databases_log_growths_total"), + "(Databases.LogGrowths) Total number of log growths for this database", []string{"mssql_instance", "database"}, nil, ) @@ -372,14 +375,14 @@ func (c *Collector) buildDatabases() error { nil, ) c.databasesLogShrinks = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "databases_log_shrinks"), - "(Databases.LogShrinks)", + prometheus.BuildFQName(types.Namespace, Name, "databases_log_shrinks_total"), + "(Databases.LogShrinks) Total number of log shrinks for this database", []string{"mssql_instance", "database"}, nil, ) c.databasesLogTruncations = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "databases_log_truncations"), - "(Databases.LogTruncations)", + prometheus.BuildFQName(types.Namespace, Name, "databases_log_truncations_total"), + "(Databases.LogTruncations) Total number of log truncations for this database", []string{"mssql_instance", "database"}, nil, ) @@ -426,8 +429,14 @@ func (c *Collector) buildDatabases() error { nil, ) c.databasesXTPControllerDLCLatencyPerFetch = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "databases_xtp_controller_dlc_fetch_latency_seconds"), - "(Databases.XTPControllerDLCLatencyPerFetch)", + prometheus.BuildFQName(types.Namespace, Name, "databases_xtp_controller_dlc_fetch_latency_seconds_total"), + "(Databases.XTPControllerDLCLatencyPerFetch) Total latency of fetches from the Direct Log Consumer by the XTP controller, in seconds", + []string{"mssql_instance", "database"}, + nil, + ) + c.databasesXTPControllerDLCFetches = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "databases_xtp_controller_dlc_fetches_total"), + "(Databases.XTPControllerDLCLatencyBase) Total number of fetches from the Direct Log Consumer by the XTP controller", []string{"mssql_instance", "database"}, nil, ) @@ -587,21 +596,21 @@ func (c *Collector) emitDatabasesMetrics(ch chan<- prometheus.Metric, sqlInstanc ch <- prometheus.MustNewConstMetric( c.databasesLogFlushWaitTime, - prometheus.GaugeValue, + prometheus.CounterValue, data.DatabasesLogFlushWaitTime/1000.0, sqlInstance.name, data.Name, ) ch <- prometheus.MustNewConstMetric( c.databasesLogFlushWriteTimeMS, - prometheus.GaugeValue, + prometheus.CounterValue, data.DatabasesLogFlushWriteTimeMS/1000.0, sqlInstance.name, data.Name, ) ch <- prometheus.MustNewConstMetric( c.databasesLogGrowths, - prometheus.GaugeValue, + prometheus.CounterValue, data.DatabasesLogGrowths, sqlInstance.name, data.Name, ) @@ -713,14 +722,14 @@ func (c *Collector) emitDatabasesMetrics(ch chan<- prometheus.Metric, sqlInstanc ch <- prometheus.MustNewConstMetric( c.databasesLogShrinks, - prometheus.GaugeValue, + prometheus.CounterValue, data.DatabasesLogShrinks, sqlInstance.name, data.Name, ) ch <- prometheus.MustNewConstMetric( c.databasesLogTruncations, - prometheus.GaugeValue, + prometheus.CounterValue, data.DatabasesLogTruncations, sqlInstance.name, data.Name, ) @@ -776,8 +785,15 @@ func (c *Collector) emitDatabasesMetrics(ch chan<- prometheus.Metric, sqlInstanc ch <- prometheus.MustNewConstMetric( c.databasesXTPControllerDLCLatencyPerFetch, - prometheus.GaugeValue, - data.DatabasesXTPControllerDLCLatencyPerFetch, + prometheus.CounterValue, + data.DatabasesXTPControllerDLCLatencyPerFetch/1e6, // microseconds + sqlInstance.name, data.Name, + ) + + ch <- prometheus.MustNewConstMetric( + c.databasesXTPControllerDLCFetches, + prometheus.CounterValue, + data.DatabasesXTPControllerDLCLatencyPerFetchBase, sqlInstance.name, data.Name, ) diff --git a/internal/collector/mssql/mssql_database_replica.go b/internal/collector/mssql/mssql_database_replica.go index 0510bfd9..ff1c84e3 100644 --- a/internal/collector/mssql/mssql_database_replica.go +++ b/internal/collector/mssql/mssql_database_replica.go @@ -100,8 +100,8 @@ func (c *Collector) buildDatabaseReplica() error { // Win32_PerfRawData_{instance}_SQLServerDatabaseReplica c.dbReplicaDatabaseFlowControlDelay = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "dbreplica_database_flow_control_wait_seconds"), - "(DatabaseReplica.DatabaseFlowControlDelay)", + prometheus.BuildFQName(types.Namespace, Name, "dbreplica_database_flow_control_wait_seconds_total"), + "(DatabaseReplica.DatabaseFlowControlDelay) Total time spent in database flow control wait, in seconds", []string{"mssql_instance", "replica"}, nil, ) @@ -124,8 +124,8 @@ func (c *Collector) buildDatabaseReplica() error { nil, ) c.dbReplicaGroupCommitTime = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "dbreplica_group_commit_stall_seconds"), - "(DatabaseReplica.GroupCommitTime)", + prometheus.BuildFQName(types.Namespace, Name, "dbreplica_group_commit_stall_seconds_total"), + "(DatabaseReplica.GroupCommitTime) Total time transactions waited for group commit, in seconds", []string{"mssql_instance", "replica"}, nil, ) @@ -184,14 +184,14 @@ func (c *Collector) buildDatabaseReplica() error { nil, ) c.dbReplicaLogremainingforundo = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "dbreplica_log_remaining_for_undo"), - "(DatabaseReplica.Logremainingforundo)", + prometheus.BuildFQName(types.Namespace, Name, "dbreplica_log_remaining_for_undo_bytes"), + "(DatabaseReplica.Logremainingforundo) Log remaining to finish the undo phase, in bytes", []string{"mssql_instance", "replica"}, nil, ) c.dbReplicaLogSendQueue = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "dbreplica_log_send_queue"), - "(DatabaseReplica.LogSendQueue)", + prometheus.BuildFQName(types.Namespace, Name, "dbreplica_log_send_queue_bytes"), + "(DatabaseReplica.LogSendQueue) Log waiting to be sent to the database replica, in bytes", []string{"mssql_instance", "replica"}, nil, ) @@ -202,8 +202,8 @@ func (c *Collector) buildDatabaseReplica() error { nil, ) c.dbReplicaRecoveryQueue = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "dbreplica_recovery_queue_records"), - "(DatabaseReplica.RecoveryQueue)", + prometheus.BuildFQName(types.Namespace, Name, "dbreplica_recovery_queue_bytes"), + "(DatabaseReplica.RecoveryQueue) Hardened log waiting to be redone on the secondary, in bytes", []string{"mssql_instance", "replica"}, nil, ) @@ -215,7 +215,7 @@ func (c *Collector) buildDatabaseReplica() error { ) c.dbReplicaRedoBytesRemaining = prometheus.NewDesc( prometheus.BuildFQName(types.Namespace, Name, "dbreplica_redo_remaining_bytes"), - "(DatabaseReplica.RedoBytesRemaining)", + "(DatabaseReplica.RedoBytesRemaining) Log remaining to be redone to finish the reverting phase, in bytes", []string{"mssql_instance", "replica"}, nil, ) @@ -232,14 +232,14 @@ func (c *Collector) buildDatabaseReplica() error { nil, ) c.dbReplicaTotalLogrequiringundo = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "dbreplica_total_log_requiring_undo"), - "(DatabaseReplica.TotalLogrequiringundo)", + prometheus.BuildFQName(types.Namespace, Name, "dbreplica_total_log_requiring_undo_bytes"), + "(DatabaseReplica.TotalLogrequiringundo) Log that needs to be undone, in bytes", []string{"mssql_instance", "replica"}, nil, ) c.dbReplicaTransactionDelay = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "dbreplica_transaction_delay_seconds"), - "(DatabaseReplica.TransactionDelay)", + prometheus.BuildFQName(types.Namespace, Name, "dbreplica_transaction_delay_seconds_total"), + "(DatabaseReplica.TransactionDelay) Total time transaction termination waited for acknowledgement, in seconds", []string{"mssql_instance", "replica"}, nil, ) @@ -266,7 +266,7 @@ func (c *Collector) emitDatabaseReplicaMetrics(ch chan<- prometheus.Metric, sqlI for _, data := range c.dbReplicaPerfDataObject { ch <- prometheus.MustNewConstMetric( c.dbReplicaDatabaseFlowControlDelay, - prometheus.GaugeValue, + prometheus.CounterValue, data.DbReplicaDatabaseFlowControlDelay/1e6, sqlInstance.name, data.Name, ) @@ -294,7 +294,7 @@ func (c *Collector) emitDatabaseReplicaMetrics(ch chan<- prometheus.Metric, sqlI ch <- prometheus.MustNewConstMetric( c.dbReplicaGroupCommitTime, - prometheus.GaugeValue, + prometheus.CounterValue, data.DbReplicaGroupCommitTime/1e6, sqlInstance.name, data.Name, ) @@ -365,14 +365,14 @@ func (c *Collector) emitDatabaseReplicaMetrics(ch chan<- prometheus.Metric, sqlI ch <- prometheus.MustNewConstMetric( c.dbReplicaLogremainingforundo, prometheus.GaugeValue, - data.DbReplicaLogRemainingForUndo, + data.DbReplicaLogRemainingForUndo*1024, sqlInstance.name, data.Name, ) ch <- prometheus.MustNewConstMetric( c.dbReplicaLogSendQueue, prometheus.GaugeValue, - data.DbReplicaLogSendQueue, + data.DbReplicaLogSendQueue*1024, sqlInstance.name, data.Name, ) @@ -386,7 +386,7 @@ func (c *Collector) emitDatabaseReplicaMetrics(ch chan<- prometheus.Metric, sqlI ch <- prometheus.MustNewConstMetric( c.dbReplicaRecoveryQueue, prometheus.GaugeValue, - data.DbReplicaRecoveryQueue, + data.DbReplicaRecoveryQueue*1024, sqlInstance.name, data.Name, ) @@ -400,7 +400,7 @@ func (c *Collector) emitDatabaseReplicaMetrics(ch chan<- prometheus.Metric, sqlI ch <- prometheus.MustNewConstMetric( c.dbReplicaRedoBytesRemaining, prometheus.GaugeValue, - data.DbReplicaRedoBytesRemaining, + data.DbReplicaRedoBytesRemaining*1024, sqlInstance.name, data.Name, ) @@ -421,13 +421,13 @@ func (c *Collector) emitDatabaseReplicaMetrics(ch chan<- prometheus.Metric, sqlI ch <- prometheus.MustNewConstMetric( c.dbReplicaTotalLogrequiringundo, prometheus.GaugeValue, - data.DbReplicaTotalLogRequiringUndo, + data.DbReplicaTotalLogRequiringUndo*1024, sqlInstance.name, data.Name, ) ch <- prometheus.MustNewConstMetric( c.dbReplicaTransactionDelay, - prometheus.GaugeValue, + prometheus.CounterValue, data.DbReplicaTransactionDelay/1000.0, sqlInstance.name, data.Name, ) diff --git a/internal/collector/mssql/mssql_general_statistics.go b/internal/collector/mssql/mssql_general_statistics.go index 1b8c6ea6..ee96f04e 100644 --- a/internal/collector/mssql/mssql_general_statistics.go +++ b/internal/collector/mssql/mssql_general_statistics.go @@ -116,7 +116,7 @@ func (c *Collector) buildGeneralStatistics() error { nil, ) c.genStatsHTTPAuthenticatedRequests = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "genstats_http_authenticated_requests"), + prometheus.BuildFQName(types.Namespace, Name, "genstats_http_authenticated_requests_total"), "(GeneralStatistics.HTTPAuthenticatedRequests)", []string{"mssql_instance"}, nil, @@ -140,7 +140,7 @@ func (c *Collector) buildGeneralStatistics() error { nil, ) c.genStatsMarsDeadlocks = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "genstats_mars_deadlocks"), + prometheus.BuildFQName(types.Namespace, Name, "genstats_mars_deadlocks_total"), "(GeneralStatistics.MarsDeadlocks)", []string{"mssql_instance"}, nil, @@ -158,55 +158,55 @@ func (c *Collector) buildGeneralStatistics() error { nil, ) c.genStatsSOAPEmptyRequests = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "genstats_soap_empty_requests"), + prometheus.BuildFQName(types.Namespace, Name, "genstats_soap_empty_requests_total"), "(GeneralStatistics.SOAPEmptyRequests)", []string{"mssql_instance"}, nil, ) c.genStatsSOAPMethodInvocations = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "genstats_soap_method_invocations"), + prometheus.BuildFQName(types.Namespace, Name, "genstats_soap_method_invocations_total"), "(GeneralStatistics.SOAPMethodInvocations)", []string{"mssql_instance"}, nil, ) c.genStatsSOAPSessionInitiateRequests = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "genstats_soap_session_initiate_requests"), + prometheus.BuildFQName(types.Namespace, Name, "genstats_soap_session_initiate_requests_total"), "(GeneralStatistics.SOAPSessionInitiateRequests)", []string{"mssql_instance"}, nil, ) c.genStatsSOAPSessionTerminateRequests = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "genstats_soap_session_terminate_requests"), + prometheus.BuildFQName(types.Namespace, Name, "genstats_soap_session_terminate_requests_total"), "(GeneralStatistics.SOAPSessionTerminateRequests)", []string{"mssql_instance"}, nil, ) c.genStatsSOAPSQLRequests = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "genstats_soapsql_requests"), + prometheus.BuildFQName(types.Namespace, Name, "genstats_soapsql_requests_total"), "(GeneralStatistics.SOAPSQLRequests)", []string{"mssql_instance"}, nil, ) c.genStatsSOAPWSDLRequests = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "genstats_soapwsdl_requests"), + prometheus.BuildFQName(types.Namespace, Name, "genstats_soapwsdl_requests_total"), "(GeneralStatistics.SOAPWSDLRequests)", []string{"mssql_instance"}, nil, ) c.genStatsSQLTraceIOProviderLockWaits = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "genstats_sql_trace_io_provider_lock_waits"), + prometheus.BuildFQName(types.Namespace, Name, "genstats_sql_trace_io_provider_lock_waits_total"), "(GeneralStatistics.SQLTraceIOProviderLockWaits)", []string{"mssql_instance"}, nil, ) c.genStatsTempDBRecoveryUnitID = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "genstats_tempdb_recovery_unit_ids_generated"), + prometheus.BuildFQName(types.Namespace, Name, "genstats_tempdb_recovery_unit_ids_generated_total"), "(GeneralStatistics.Tempdbrecoveryunitid)", []string{"mssql_instance"}, nil, ) c.genStatsTempDBrowSetID = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "genstats_tempdb_rowset_ids_generated"), + prometheus.BuildFQName(types.Namespace, Name, "genstats_tempdb_rowset_ids_generated_total"), "(GeneralStatistics.Tempdbrowsetid)", []string{"mssql_instance"}, nil, @@ -255,6 +255,12 @@ func (c *Collector) collectGeneralStatisticsInstance(ch chan<- prometheus.Metric return fmt.Errorf("failed to collect %s metrics: %w", c.mssqlGetPerfObjectName(sqlInstance, "General Statistics"), err) } + c.emitGeneralStatisticsMetrics(ch, sqlInstance) + + return nil +} + +func (c *Collector) emitGeneralStatisticsMetrics(ch chan<- prometheus.Metric, sqlInstance mssqlInstance) { ch <- prometheus.MustNewConstMetric( c.genStatsActiveTempTables, prometheus.GaugeValue, @@ -278,7 +284,7 @@ func (c *Collector) collectGeneralStatisticsInstance(ch chan<- prometheus.Metric ch <- prometheus.MustNewConstMetric( c.genStatsHTTPAuthenticatedRequests, - prometheus.GaugeValue, + prometheus.CounterValue, c.genStatsPerfDataObject[0].GenStatsHTTPAuthenticatedRequests, sqlInstance.name, ) @@ -306,7 +312,7 @@ func (c *Collector) collectGeneralStatisticsInstance(ch chan<- prometheus.Metric ch <- prometheus.MustNewConstMetric( c.genStatsMarsDeadlocks, - prometheus.GaugeValue, + prometheus.CounterValue, c.genStatsPerfDataObject[0].GenStatsMarsDeadlocks, sqlInstance.name, ) @@ -327,63 +333,63 @@ func (c *Collector) collectGeneralStatisticsInstance(ch chan<- prometheus.Metric ch <- prometheus.MustNewConstMetric( c.genStatsSOAPEmptyRequests, - prometheus.GaugeValue, + prometheus.CounterValue, c.genStatsPerfDataObject[0].GenStatsSOAPEmptyRequests, sqlInstance.name, ) ch <- prometheus.MustNewConstMetric( c.genStatsSOAPMethodInvocations, - prometheus.GaugeValue, + prometheus.CounterValue, c.genStatsPerfDataObject[0].GenStatsSOAPMethodInvocations, sqlInstance.name, ) ch <- prometheus.MustNewConstMetric( c.genStatsSOAPSessionInitiateRequests, - prometheus.GaugeValue, + prometheus.CounterValue, c.genStatsPerfDataObject[0].GenStatsSOAPSessionInitiateRequests, sqlInstance.name, ) ch <- prometheus.MustNewConstMetric( c.genStatsSOAPSessionTerminateRequests, - prometheus.GaugeValue, + prometheus.CounterValue, c.genStatsPerfDataObject[0].GenStatsSOAPSessionTerminateRequests, sqlInstance.name, ) ch <- prometheus.MustNewConstMetric( c.genStatsSOAPSQLRequests, - prometheus.GaugeValue, + prometheus.CounterValue, c.genStatsPerfDataObject[0].GenStatsSOAPSQLRequests, sqlInstance.name, ) ch <- prometheus.MustNewConstMetric( c.genStatsSOAPWSDLRequests, - prometheus.GaugeValue, + prometheus.CounterValue, c.genStatsPerfDataObject[0].GenStatsSOAPWSDLRequests, sqlInstance.name, ) ch <- prometheus.MustNewConstMetric( c.genStatsSQLTraceIOProviderLockWaits, - prometheus.GaugeValue, + prometheus.CounterValue, c.genStatsPerfDataObject[0].GenStatsSQLTraceIOProviderLockWaits, sqlInstance.name, ) ch <- prometheus.MustNewConstMetric( c.genStatsTempDBRecoveryUnitID, - prometheus.GaugeValue, + prometheus.CounterValue, c.genStatsPerfDataObject[0].GenStatsTempdbRecoveryUnitID, sqlInstance.name, ) ch <- prometheus.MustNewConstMetric( c.genStatsTempDBrowSetID, - prometheus.GaugeValue, + prometheus.CounterValue, c.genStatsPerfDataObject[0].GenStatsTempdbRowsetID, sqlInstance.name, ) @@ -422,8 +428,6 @@ func (c *Collector) collectGeneralStatisticsInstance(ch chan<- prometheus.Metric c.genStatsPerfDataObject[0].GenStatsUserConnections, sqlInstance.name, ) - - return nil } func (c *Collector) closeGeneralStatistics() { diff --git a/internal/collector/mssql/mssql_locks.go b/internal/collector/mssql/mssql_locks.go index b7bbe1a3..a550e094 100644 --- a/internal/collector/mssql/mssql_locks.go +++ b/internal/collector/mssql/mssql_locks.go @@ -68,14 +68,14 @@ func (c *Collector) buildLocks() error { } c.locksWaitTime = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "locks_wait_time_seconds"), - "(Locks.AverageWaitTimems Total time in seconds which locks have been holding resources)", + prometheus.BuildFQName(types.Namespace, Name, "locks_wait_time_seconds_total"), + "(Locks.AverageWaitTimems) Total time lock requests waited, in seconds. Numerator of Average Wait Time (ms)", []string{"mssql_instance", "resource"}, nil, ) c.locksCount = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "locks_count"), - "(Locks.AverageWaitTimems_Base count of how often requests have run into locks)", + prometheus.BuildFQName(types.Namespace, Name, "locks_count_total"), + "(Locks.AverageWaitTimems_Base) Total number of lock requests that waited. Base of Average Wait Time (ms)", []string{"mssql_instance", "resource"}, nil, ) @@ -104,8 +104,8 @@ func (c *Collector) buildLocks() error { nil, ) c.locksLockWaitTimeMS = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "locks_lock_wait_seconds"), - "(Locks.LockWaitTimems)", + prometheus.BuildFQName(types.Namespace, Name, "locks_lock_wait_seconds_total"), + "(Locks.LockWaitTimems) Total time lock requests waited, in seconds", []string{"mssql_instance", "resource"}, nil, ) @@ -138,14 +138,14 @@ func (c *Collector) emitLocksMetrics(ch chan<- prometheus.Metric, sqlInstance ms for _, data := range c.locksPerfDataObject { ch <- prometheus.MustNewConstMetric( c.locksWaitTime, - prometheus.GaugeValue, + prometheus.CounterValue, data.LocksAverageWaitTimeMS/1000.0, sqlInstance.name, data.Name, ) ch <- prometheus.MustNewConstMetric( c.locksCount, - prometheus.GaugeValue, + prometheus.CounterValue, data.LocksAverageWaitTimeMSBase, sqlInstance.name, data.Name, ) @@ -180,7 +180,7 @@ func (c *Collector) emitLocksMetrics(ch chan<- prometheus.Metric, sqlInstance ms ch <- prometheus.MustNewConstMetric( c.locksLockWaitTimeMS, - prometheus.GaugeValue, + prometheus.CounterValue, data.LocksLockWaitTimeMS/1000.0, sqlInstance.name, data.Name, ) diff --git a/internal/collector/mssql/mssql_transactions.go b/internal/collector/mssql/mssql_transactions.go index 42bdf8e8..c59d9394 100644 --- a/internal/collector/mssql/mssql_transactions.go +++ b/internal/collector/mssql/mssql_transactions.go @@ -32,13 +32,14 @@ type collectorTransactions struct { transactionsTempDbFreeSpaceBytes *prometheus.Desc transactionsLongestTransactionRunningSeconds *prometheus.Desc - transactionsNonSnapshotVersionActiveTotal *prometheus.Desc - transactionsSnapshotActiveTotal *prometheus.Desc + transactionsNonSnapshotVersionActive *prometheus.Desc + transactionsSnapshotActive *prometheus.Desc transactionsActive *prometheus.Desc transactionsUpdateConflictsTotal *prometheus.Desc - transactionsUpdateSnapshotActiveTotal *prometheus.Desc - transactionsVersionCleanupRateBytes *prometheus.Desc - transactionsVersionGenerationRateBytes *prometheus.Desc + transactionsUpdateSnapshotTotal *prometheus.Desc + transactionsUpdateSnapshotActive *prometheus.Desc + transactionsVersionCleanupBytes *prometheus.Desc + transactionsVersionGenerationBytes *prometheus.Desc transactionsVersionStoreSizeBytes *prometheus.Desc transactionsVersionStoreUnits *prometheus.Desc transactionsVersionStoreCreationUnits *prometheus.Desc @@ -52,6 +53,7 @@ type perfDataCounterValuesTransactions struct { TransactionsSnapshotTransactions float64 `perfdata:"Snapshot Transactions"` TransactionsTransactions float64 `perfdata:"Transactions"` TransactionsUpdateconflictratio float64 `perfdata:"Update conflict ratio"` + TransactionsUpdateconflictratioBase float64 `perfdata:"Update conflict ratio,secondvalue"` TransactionsUpdateSnapshotTransactions float64 `perfdata:"Update Snapshot Transactions"` TransactionsVersionCleanuprateKBPers float64 `perfdata:"Version Cleanup rate (KB/s)"` TransactionsVersionGenerationrateKBPers float64 `perfdata:"Version Generation rate (KB/s)"` @@ -86,14 +88,14 @@ func (c *Collector) buildTransactions() error { []string{"mssql_instance"}, nil, ) - c.transactionsNonSnapshotVersionActiveTotal = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "transactions_nonsnapshot_version_active_total"), + c.transactionsNonSnapshotVersionActive = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "transactions_nonsnapshot_version_active"), "(Transactions.NonSnapshotVersionTransactions)", []string{"mssql_instance"}, nil, ) - c.transactionsSnapshotActiveTotal = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "transactions_snapshot_active_total"), + c.transactionsSnapshotActive = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "transactions_snapshot_active"), "(Transactions.SnapshotTransactions)", []string{"mssql_instance"}, nil, @@ -106,25 +108,31 @@ func (c *Collector) buildTransactions() error { ) c.transactionsUpdateConflictsTotal = prometheus.NewDesc( prometheus.BuildFQName(types.Namespace, Name, "transactions_update_conflicts_total"), - "(Transactions.UpdateConflictRatio)", + "(Transactions.UpdateConflictRatio) Total number of update snapshot transactions that had an update conflict", []string{"mssql_instance"}, nil, ) - c.transactionsUpdateSnapshotActiveTotal = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "transactions_update_snapshot_active_total"), + c.transactionsUpdateSnapshotTotal = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "transactions_update_snapshot_transactions_total"), + "(Transactions.UpdateConflictRatio_Base) Total number of update snapshot transactions", + []string{"mssql_instance"}, + nil, + ) + c.transactionsUpdateSnapshotActive = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "transactions_update_snapshot_active"), "(Transactions.UpdateSnapshotTransactions)", []string{"mssql_instance"}, nil, ) - c.transactionsVersionCleanupRateBytes = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "transactions_version_cleanup_rate_bytes"), - "(Transactions.VersionCleanupRateKBs)", + c.transactionsVersionCleanupBytes = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "transactions_version_cleanup_bytes_total"), + "(Transactions.VersionCleanupRateKBs) Total size of row versions removed from the version store, in bytes", []string{"mssql_instance"}, nil, ) - c.transactionsVersionGenerationRateBytes = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "transactions_version_generation_rate_bytes"), - "(Transactions.VersionGenerationRateKBs)", + c.transactionsVersionGenerationBytes = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "transactions_version_generation_bytes_total"), + "(Transactions.VersionGenerationRateKBs) Total size of row versions added to the version store, in bytes", []string{"mssql_instance"}, nil, ) @@ -168,6 +176,12 @@ func (c *Collector) collectTransactionsInstance(ch chan<- prometheus.Metric, sql return fmt.Errorf("failed to collect %s metrics: %w", c.mssqlGetPerfObjectName(sqlInstance, "Transactions"), err) } + c.emitTransactionsMetrics(ch, sqlInstance) + + return nil +} + +func (c *Collector) emitTransactionsMetrics(ch chan<- prometheus.Metric, sqlInstance mssqlInstance) { ch <- prometheus.MustNewConstMetric( c.transactionsTempDbFreeSpaceBytes, prometheus.GaugeValue, @@ -183,15 +197,15 @@ func (c *Collector) collectTransactionsInstance(ch chan<- prometheus.Metric, sql ) ch <- prometheus.MustNewConstMetric( - c.transactionsNonSnapshotVersionActiveTotal, - prometheus.CounterValue, + c.transactionsNonSnapshotVersionActive, + prometheus.GaugeValue, c.transactionsPerfDataObject[0].TransactionsNonSnapshotVersionTransactions, sqlInstance.name, ) ch <- prometheus.MustNewConstMetric( - c.transactionsSnapshotActiveTotal, - prometheus.CounterValue, + c.transactionsSnapshotActive, + prometheus.GaugeValue, c.transactionsPerfDataObject[0].TransactionsSnapshotTransactions, sqlInstance.name, ) @@ -211,22 +225,29 @@ func (c *Collector) collectTransactionsInstance(ch chan<- prometheus.Metric, sql ) ch <- prometheus.MustNewConstMetric( - c.transactionsUpdateSnapshotActiveTotal, + c.transactionsUpdateSnapshotTotal, prometheus.CounterValue, + c.transactionsPerfDataObject[0].TransactionsUpdateconflictratioBase, + sqlInstance.name, + ) + + ch <- prometheus.MustNewConstMetric( + c.transactionsUpdateSnapshotActive, + prometheus.GaugeValue, c.transactionsPerfDataObject[0].TransactionsUpdateSnapshotTransactions, sqlInstance.name, ) ch <- prometheus.MustNewConstMetric( - c.transactionsVersionCleanupRateBytes, - prometheus.GaugeValue, + c.transactionsVersionCleanupBytes, + prometheus.CounterValue, c.transactionsPerfDataObject[0].TransactionsVersionCleanuprateKBPers*1024, sqlInstance.name, ) ch <- prometheus.MustNewConstMetric( - c.transactionsVersionGenerationRateBytes, - prometheus.GaugeValue, + c.transactionsVersionGenerationBytes, + prometheus.CounterValue, c.transactionsPerfDataObject[0].TransactionsVersionGenerationrateKBPers*1024, sqlInstance.name, ) @@ -240,7 +261,7 @@ func (c *Collector) collectTransactionsInstance(ch chan<- prometheus.Metric, sql ch <- prometheus.MustNewConstMetric( c.transactionsVersionStoreUnits, - prometheus.CounterValue, + prometheus.GaugeValue, c.transactionsPerfDataObject[0].TransactionsVersionStoreunitcount, sqlInstance.name, ) @@ -258,8 +279,6 @@ func (c *Collector) collectTransactionsInstance(ch chan<- prometheus.Metric, sql c.transactionsPerfDataObject[0].TransactionsVersionStoreunittruncation, sqlInstance.name, ) - - return nil } func (c *Collector) closeTransactions() { diff --git a/internal/collector/mssql/mssql_wait_stats.go b/internal/collector/mssql/mssql_wait_stats.go index 311631fc..c56ee413 100644 --- a/internal/collector/mssql/mssql_wait_stats.go +++ b/internal/collector/mssql/mssql_wait_stats.go @@ -161,93 +161,102 @@ func (c *Collector) collectWaitStatsInstance(ch chan<- prometheus.Metric, sqlIns return fmt.Errorf("failed to collect %s metrics: %w", c.mssqlGetPerfObjectName(sqlInstance, "Wait Statistics"), err) } + c.emitWaitStatsMetrics(ch, sqlInstance) + + return nil +} + +// emitWaitStatsMetrics publishes every wait statistic as a gauge. The instance +// (label "item") names the statistic: "Average wait time (ms)", +// "Cumulative wait time (ms) per second", "Waits in progress" and +// "Waits started per second". SQL Server computes them over its most recent +// sampling interval, so none of them only grows. +func (c *Collector) emitWaitStatsMetrics(ch chan<- prometheus.Metric, sqlInstance mssqlInstance) { for _, data := range c.waitStatsPerfDataObject { ch <- prometheus.MustNewConstMetric( c.waitStatsLockWaits, - prometheus.CounterValue, + prometheus.GaugeValue, data.WaitStatsLockWaits, sqlInstance.name, data.Name, ) ch <- prometheus.MustNewConstMetric( c.waitStatsMemoryGrantQueueWaits, - prometheus.CounterValue, + prometheus.GaugeValue, data.WaitStatsMemoryGrantQueueWaits, sqlInstance.name, data.Name, ) ch <- prometheus.MustNewConstMetric( c.waitStatsThreadSafeMemoryObjectsWaits, - prometheus.CounterValue, + prometheus.GaugeValue, data.WaitStatsThreadSafeMemoryObjectsWaits, sqlInstance.name, data.Name, ) ch <- prometheus.MustNewConstMetric( c.waitStatsLogWriteWaits, - prometheus.CounterValue, + prometheus.GaugeValue, data.WaitStatsLogWriteWaits, sqlInstance.name, data.Name, ) ch <- prometheus.MustNewConstMetric( c.waitStatsLogBufferWaits, - prometheus.CounterValue, + prometheus.GaugeValue, data.WaitStatsLogBufferWaits, sqlInstance.name, data.Name, ) ch <- prometheus.MustNewConstMetric( c.waitStatsNetworkIOWaits, - prometheus.CounterValue, + prometheus.GaugeValue, data.WaitStatsNetworkIOWaits, sqlInstance.name, data.Name, ) ch <- prometheus.MustNewConstMetric( c.waitStatsPageIOLatchWaits, - prometheus.CounterValue, + prometheus.GaugeValue, data.WaitStatsPageIOLatchWaits, sqlInstance.name, data.Name, ) ch <- prometheus.MustNewConstMetric( c.waitStatsPageLatchWaits, - prometheus.CounterValue, + prometheus.GaugeValue, data.WaitStatsPageLatchWaits, sqlInstance.name, data.Name, ) ch <- prometheus.MustNewConstMetric( c.waitStatsNonPageLatchWaits, - prometheus.CounterValue, + prometheus.GaugeValue, data.WaitStatsNonpageLatchWaits, sqlInstance.name, data.Name, ) ch <- prometheus.MustNewConstMetric( c.waitStatsWaitForTheWorkerWaits, - prometheus.CounterValue, + prometheus.GaugeValue, data.WaitStatsWaitForTheWorkerWaits, sqlInstance.name, data.Name, ) ch <- prometheus.MustNewConstMetric( c.waitStatsWorkspaceSynchronizationWaits, - prometheus.CounterValue, + prometheus.GaugeValue, data.WaitStatsWorkspaceSynchronizationWaits, sqlInstance.name, data.Name, ) ch <- prometheus.MustNewConstMetric( c.waitStatsTransactionOwnershipWaits, - prometheus.CounterValue, + prometheus.GaugeValue, data.WaitStatsTransactionOwnershipWaits, sqlInstance.name, data.Name, ) } - - return nil } func (c *Collector) closeWaitStats() {