mirror of
https://github.com/prometheus-community/windows_exporter.git
synced 2026-10-17 01:59:04 +02:00
adfs: convert database query times with the counter time base (#2719)
Signed-off-by: Jan-Otto Kröpke <mail@jkroepke.de>
This commit is contained in:
@@ -103,6 +103,10 @@ exercised a live Windows role.
|
||||
appropriate. Raw PDH values can be cumulative numerators, ticks or counts,
|
||||
even when the Windows counter name says average, percentage or seconds.
|
||||
Convert units explicitly; expose the base/operation count needed for ratios.
|
||||
The raw value of a `PERF_AVERAGE_TIMER` counter is in ticks of its time
|
||||
base: in new code, divide it by `pdh.Collector.Frequency` instead of a fixed
|
||||
factor. The disk and `smbclient` collectors still use the fixed 1e-7
|
||||
(10 MHz) factor.
|
||||
Never publish an invalid sample as zero: that can look like a counter reset.
|
||||
- Keep the typed PDH collectors and `perfdata` tags. Preserve duplicate
|
||||
instance occurrences within a sample; their suffixes are not stable identities.
|
||||
|
||||
+2
-1
@@ -19,7 +19,7 @@ Most installations need no changes. Go through the checklist below and read the
|
||||
| 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, Hyper-V, logical disk queue, cache, IIS output cache, memory, RemoteFX, 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 or RemoteFX skipped frames | Expect corrected values | [Corrected values](#corrected-values) |
|
||||
| You chart SQL Server latencies, SQL Server lock counts, DHCP denials, AD LSA writes, AD computer creations, AD FS database query times 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) |
|
||||
@@ -311,6 +311,7 @@ These metrics keep their names, but 0.31 reported wrong values. Graphs show a st
|
||||
- `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_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_netframework_clrmemory_gc_time_percent` no longer overflows, and it is left out when Windows reports no base value. The `netframework` collector now reads the CLR counters through PDH instead of WMI; names, labels and types are unchanged.
|
||||
|
||||
@@ -57,9 +57,11 @@ Name | Description | Type | Labels
|
||||
`windows_adfs_external_authentications_failure_total` | Total number of failed authentications from external MFA providers | counter | None
|
||||
`windows_adfs_external_authentications_success_total` | Total number of successful authentications from external MFA providers | counter | None
|
||||
`windows_adfs_db_artifact_failure_total` | Total number of failures connecting to the artifact database | counter | None
|
||||
`windows_adfs_db_artifact_query_time_seconds_total` | Accumulator of time taken for an artifact database query | counter | None
|
||||
`windows_adfs_db_artifact_queries_total` | Total number of artifact database queries | counter | None
|
||||
`windows_adfs_db_artifact_query_time_seconds_total` | Total time spent on artifact database queries, in seconds | counter | None
|
||||
`windows_adfs_db_config_failure_total` | Total number of failures connecting to the configuration database | counter | None
|
||||
`windows_adfs_db_config_query_time_seconds_total` | Accumulator of time taken for a configuration database query | counter | None
|
||||
`windows_adfs_db_config_queries_total` | Total number of configuration database queries | counter | None
|
||||
`windows_adfs_db_config_query_time_seconds_total` | Total time spent on configuration database queries, in seconds | counter | None
|
||||
`windows_adfs_federation_metadata_requests_total` | Total number of Federation Metadata requests | counter | None
|
||||
|
||||
### Example metric
|
||||
@@ -74,6 +76,7 @@ rate(windows_adfs_device_authentications_total[2m])
|
||||
|---|----|
|
||||
|`rate(windows_adfs_oauth_password_grant_requests_failure_total[5m])`| Rate of OAuth requests failing due to bad client/resource values|
|
||||
|`rate(windows_adfs_userpassword_authentications_failure_total[5m])`| Rate of `/adfs/oauth2/token/` requests failing due to bad username/password values (possible credential spraying)|
|
||||
|`rate(windows_adfs_db_config_query_time_seconds_total[5m]) / rate(windows_adfs_db_config_queries_total[5m])`| Average configuration database query time in seconds|
|
||||
|
||||
## Alerting examples
|
||||
**prometheus.rules**
|
||||
|
||||
@@ -22,9 +22,9 @@ one.
|
||||
duration in ticks of the counter's time base (`PdhGetCounterTimeBase`,
|
||||
10 MHz on the test host, same as `QueryPerformanceFrequency`); the base
|
||||
(`,secondvalue`) is the number of operations. `pdh.CounterTypeRaw` does not
|
||||
convert this type, and `internal/pdh` reads the time base only for
|
||||
`PERF_ELAPSED_TIME`. Seconds are `raw / time base`; do not hardcode a scale
|
||||
factor.
|
||||
convert this type; `internal/pdh` reads its time base, and the collector
|
||||
divides the raw value by `pdh.Collector.Frequency` to get seconds. Do not
|
||||
hardcode a scale factor.
|
||||
- Sample after about one hour: `Average Config Database Query Time` raw
|
||||
11521176, base 306, which is 1.152 s in total, 3.8 ms per query.
|
||||
`Federation Metadata Requests` was 1, all authentication counters 0.
|
||||
|
||||
+107
-63
@@ -20,7 +20,6 @@ package adfs
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/alecthomas/kingpin/v2"
|
||||
@@ -45,8 +44,10 @@ type Collector struct {
|
||||
|
||||
adLoginConnectionFailures *prometheus.Desc
|
||||
artifactDBFailures *prometheus.Desc
|
||||
avgArtifactDBQueryTime *prometheus.Desc
|
||||
avgConfigDBQueryTime *prometheus.Desc
|
||||
artifactDBQueries *prometheus.Desc
|
||||
artifactDBQueryTime *prometheus.Desc
|
||||
configDBQueries *prometheus.Desc
|
||||
configDBQueryTime *prometheus.Desc
|
||||
certificateAuthentications *prometheus.Desc
|
||||
configDBFailures *prometheus.Desc
|
||||
deviceAuthentications *prometheus.Desc
|
||||
@@ -115,6 +116,19 @@ func (c *Collector) Close() error {
|
||||
}
|
||||
|
||||
func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error {
|
||||
c.buildDescriptors()
|
||||
|
||||
var err error
|
||||
|
||||
c.perfDataCollector, err = pdh.NewCollector[perfDataCounterValues](logger.With(slog.String("collector", Name)), pdh.CounterTypeRaw, "AD FS", nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create AD FS collector: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Collector) buildDescriptors() {
|
||||
c.adLoginConnectionFailures = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "ad_login_connection_failures_total"),
|
||||
"Total number of connection failures to an Active Directory domain controller",
|
||||
@@ -349,9 +363,15 @@ func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error {
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
c.avgArtifactDBQueryTime = prometheus.NewDesc(
|
||||
c.artifactDBQueryTime = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "db_artifact_query_time_seconds_total"),
|
||||
"Accumulator of time taken for an artifact database query",
|
||||
"Total time spent on artifact database queries, in seconds",
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
c.artifactDBQueries = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "db_artifact_queries_total"),
|
||||
"Total number of artifact database queries",
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
@@ -361,9 +381,15 @@ func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error {
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
c.avgConfigDBQueryTime = prometheus.NewDesc(
|
||||
c.configDBQueryTime = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "db_config_query_time_seconds_total"),
|
||||
"Accumulator of time taken for a configuration database query",
|
||||
"Total time spent on configuration database queries, in seconds",
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
c.configDBQueries = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "db_config_queries_total"),
|
||||
"Total number of configuration database queries",
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
@@ -373,15 +399,6 @@ func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error {
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
|
||||
var err error
|
||||
|
||||
c.perfDataCollector, err = pdh.NewCollector[perfDataCounterValues](logger.With(slog.String("collector", Name)), pdh.CounterTypeRaw, "AD FS", nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create AD FS collector: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Collector) Collect(ch chan<- prometheus.Metric, _ time.Duration) error {
|
||||
@@ -392,263 +409,290 @@ func (c *Collector) Collect(ch chan<- prometheus.Metric, _ time.Duration) error
|
||||
return fmt.Errorf("failed to collect ADFS metrics: %w", types.ErrNoDataUnexpected)
|
||||
}
|
||||
|
||||
// The query times are PERF_AVERAGE_TIMER counters: the raw value is the
|
||||
// summed duration in ticks of the counter's time base.
|
||||
artifactDBFrequency, _ := c.perfDataCollector.Frequency(counterArtifactDBQueryTime)
|
||||
configDBFrequency, _ := c.perfDataCollector.Frequency(counterConfigDBQueryTime)
|
||||
|
||||
c.collectMetrics(ch, c.perfDataObject[0], artifactDBFrequency, configDBFrequency)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// collectMetrics sends the metrics of data. A query time is only sent if the
|
||||
// frequency of its counter is known.
|
||||
func (c *Collector) collectMetrics(ch chan<- prometheus.Metric, data perfDataCounterValues, artifactDBFrequency, configDBFrequency int64) {
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.adLoginConnectionFailures,
|
||||
prometheus.CounterValue,
|
||||
c.perfDataObject[0].AdLoginConnectionFailures,
|
||||
data.AdLoginConnectionFailures,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.certificateAuthentications,
|
||||
prometheus.CounterValue,
|
||||
c.perfDataObject[0].CertificateAuthentications,
|
||||
data.CertificateAuthentications,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.deviceAuthentications,
|
||||
prometheus.CounterValue,
|
||||
c.perfDataObject[0].DeviceAuthentications,
|
||||
data.DeviceAuthentications,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.extranetAccountLockouts,
|
||||
prometheus.CounterValue,
|
||||
c.perfDataObject[0].ExtranetAccountLockouts,
|
||||
data.ExtranetAccountLockouts,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.federatedAuthentications,
|
||||
prometheus.CounterValue,
|
||||
c.perfDataObject[0].FederatedAuthentications,
|
||||
data.FederatedAuthentications,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.passportAuthentications,
|
||||
prometheus.CounterValue,
|
||||
c.perfDataObject[0].PassportAuthentications,
|
||||
data.PassportAuthentications,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.passiveRequests,
|
||||
prometheus.CounterValue,
|
||||
c.perfDataObject[0].PassiveRequests,
|
||||
data.PassiveRequests,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.passwordChangeFailed,
|
||||
prometheus.CounterValue,
|
||||
c.perfDataObject[0].PasswordChangeFailed,
|
||||
data.PasswordChangeFailed,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.passwordChangeSucceeded,
|
||||
prometheus.CounterValue,
|
||||
c.perfDataObject[0].PasswordChangeSucceeded,
|
||||
data.PasswordChangeSucceeded,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.tokenRequests,
|
||||
prometheus.CounterValue,
|
||||
c.perfDataObject[0].TokenRequests,
|
||||
data.TokenRequests,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.windowsIntegratedAuthentications,
|
||||
prometheus.CounterValue,
|
||||
c.perfDataObject[0].WindowsIntegratedAuthentications,
|
||||
data.WindowsIntegratedAuthentications,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.oAuthAuthZRequests,
|
||||
prometheus.CounterValue,
|
||||
c.perfDataObject[0].OAuthAuthZRequests,
|
||||
data.OAuthAuthZRequests,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.oAuthClientAuthentications,
|
||||
prometheus.CounterValue,
|
||||
c.perfDataObject[0].OAuthClientAuthentications,
|
||||
data.OAuthClientAuthentications,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.oAuthClientAuthenticationsFailures,
|
||||
prometheus.CounterValue,
|
||||
c.perfDataObject[0].OAuthClientAuthenticationFailures,
|
||||
data.OAuthClientAuthenticationFailures,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.oAuthClientCredentialsRequestFailures,
|
||||
prometheus.CounterValue,
|
||||
c.perfDataObject[0].OAuthClientCredentialRequestFailures,
|
||||
data.OAuthClientCredentialRequestFailures,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.oAuthClientCredentialsRequests,
|
||||
prometheus.CounterValue,
|
||||
c.perfDataObject[0].OAuthClientCredentialRequests,
|
||||
data.OAuthClientCredentialRequests,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.oAuthClientPrivateKeyJwtAuthenticationFailures,
|
||||
prometheus.CounterValue,
|
||||
c.perfDataObject[0].OAuthClientPrivateKeyJWTAuthenticationFailures,
|
||||
data.OAuthClientPrivateKeyJWTAuthenticationFailures,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.oAuthClientPrivateKeyJwtAuthentications,
|
||||
prometheus.CounterValue,
|
||||
c.perfDataObject[0].OAuthClientPrivateKeyJWTAuthentications,
|
||||
data.OAuthClientPrivateKeyJWTAuthentications,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.oAuthClientSecretBasicAuthenticationFailures,
|
||||
prometheus.CounterValue,
|
||||
c.perfDataObject[0].OAuthClientBasicAuthenticationFailures,
|
||||
data.OAuthClientBasicAuthenticationFailures,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.oAuthClientSecretBasicAuthentications,
|
||||
prometheus.CounterValue,
|
||||
c.perfDataObject[0].OAuthClientBasicAuthentications,
|
||||
data.OAuthClientBasicAuthentications,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.oAuthClientSecretPostAuthenticationFailures,
|
||||
prometheus.CounterValue,
|
||||
c.perfDataObject[0].OAuthClientSecretPostAuthenticationFailures,
|
||||
data.OAuthClientSecretPostAuthenticationFailures,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.oAuthClientSecretPostAuthentications,
|
||||
prometheus.CounterValue,
|
||||
c.perfDataObject[0].OAuthClientSecretPostAuthentications,
|
||||
data.OAuthClientSecretPostAuthentications,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.oAuthClientWindowsIntegratedAuthenticationFailures,
|
||||
prometheus.CounterValue,
|
||||
c.perfDataObject[0].OAuthClientWindowsAuthenticationFailures,
|
||||
data.OAuthClientWindowsAuthenticationFailures,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.oAuthClientWindowsIntegratedAuthentications,
|
||||
prometheus.CounterValue,
|
||||
c.perfDataObject[0].OAuthClientWindowsAuthentications,
|
||||
data.OAuthClientWindowsAuthentications,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.oAuthLogonCertificateRequestFailures,
|
||||
prometheus.CounterValue,
|
||||
c.perfDataObject[0].OAuthLogonCertRequestFailures,
|
||||
data.OAuthLogonCertRequestFailures,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.oAuthLogonCertificateTokenRequests,
|
||||
prometheus.CounterValue,
|
||||
c.perfDataObject[0].OAuthLogonCertTokenRequests,
|
||||
data.OAuthLogonCertTokenRequests,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.oAuthPasswordGrantRequestFailures,
|
||||
prometheus.CounterValue,
|
||||
c.perfDataObject[0].OAuthPasswordGrantRequestFailures,
|
||||
data.OAuthPasswordGrantRequestFailures,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.oAuthPasswordGrantRequests,
|
||||
prometheus.CounterValue,
|
||||
c.perfDataObject[0].OAuthPasswordGrantRequests,
|
||||
data.OAuthPasswordGrantRequests,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.oAuthTokenRequests,
|
||||
prometheus.CounterValue,
|
||||
c.perfDataObject[0].OAuthTokenRequests,
|
||||
data.OAuthTokenRequests,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.samlPTokenRequests,
|
||||
prometheus.CounterValue,
|
||||
c.perfDataObject[0].SamlPTokenRequests,
|
||||
data.SamlPTokenRequests,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.ssoAuthenticationFailures,
|
||||
prometheus.CounterValue,
|
||||
c.perfDataObject[0].SsoAuthenticationFailures,
|
||||
data.SsoAuthenticationFailures,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.ssoAuthentications,
|
||||
prometheus.CounterValue,
|
||||
c.perfDataObject[0].SsoAuthentications,
|
||||
data.SsoAuthentications,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.wsFedTokenRequests,
|
||||
prometheus.CounterValue,
|
||||
c.perfDataObject[0].WsFedTokenRequests,
|
||||
data.WsFedTokenRequests,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.wsTrustTokenRequests,
|
||||
prometheus.CounterValue,
|
||||
c.perfDataObject[0].WsTrustTokenRequests,
|
||||
data.WsTrustTokenRequests,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.upAuthenticationFailures,
|
||||
prometheus.CounterValue,
|
||||
c.perfDataObject[0].UsernamePasswordAuthenticationFailures,
|
||||
data.UsernamePasswordAuthenticationFailures,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.upAuthentications,
|
||||
prometheus.CounterValue,
|
||||
c.perfDataObject[0].UsernamePasswordAuthentications,
|
||||
data.UsernamePasswordAuthentications,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.externalAuthenticationFailures,
|
||||
prometheus.CounterValue,
|
||||
c.perfDataObject[0].ExternalAuthNFailures,
|
||||
data.ExternalAuthNFailures,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.externalAuthentications,
|
||||
prometheus.CounterValue,
|
||||
c.perfDataObject[0].ExternalAuthentications,
|
||||
data.ExternalAuthentications,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.artifactDBFailures,
|
||||
prometheus.CounterValue,
|
||||
c.perfDataObject[0].ArtifactDBFailures,
|
||||
data.ArtifactDBFailures,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.avgArtifactDBQueryTime,
|
||||
c.artifactDBQueries,
|
||||
prometheus.CounterValue,
|
||||
c.perfDataObject[0].AvgArtifactDBQueryTime*math.Pow(10, -8),
|
||||
data.ArtifactDBQueries,
|
||||
)
|
||||
|
||||
if artifactDBFrequency > 0 {
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.artifactDBQueryTime,
|
||||
prometheus.CounterValue,
|
||||
data.ArtifactDBQueryTimeTicks/float64(artifactDBFrequency),
|
||||
)
|
||||
}
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.configDBFailures,
|
||||
prometheus.CounterValue,
|
||||
c.perfDataObject[0].ConfigDBFailures,
|
||||
data.ConfigDBFailures,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.avgConfigDBQueryTime,
|
||||
c.configDBQueries,
|
||||
prometheus.CounterValue,
|
||||
c.perfDataObject[0].AvgConfigDBQueryTime*math.Pow(10, -8),
|
||||
data.ConfigDBQueries,
|
||||
)
|
||||
|
||||
if configDBFrequency > 0 {
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.configDBQueryTime,
|
||||
prometheus.CounterValue,
|
||||
data.ConfigDBQueryTimeTicks/float64(configDBFrequency),
|
||||
)
|
||||
}
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.federationMetadataRequests,
|
||||
prometheus.CounterValue,
|
||||
c.perfDataObject[0].FederationMetadataRequests,
|
||||
data.FederationMetadataRequests,
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Copyright The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build windows
|
||||
|
||||
package adfs
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
dto "github.com/prometheus/client_model/go"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestCollectDatabaseQueryTimes checks that the raw ticks of the database
|
||||
// query time counters are converted to seconds with the counter's time base,
|
||||
// and that the query counts are exported.
|
||||
func TestCollectDatabaseQueryTimes(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
data := perfDataCounterValues{
|
||||
ArtifactDBQueryTimeTicks: 2_500_000,
|
||||
ArtifactDBQueries: 5,
|
||||
ConfigDBQueryTimeTicks: 11_521_176,
|
||||
ConfigDBQueries: 306,
|
||||
}
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
artifactDBFrequency int64
|
||||
configDBFrequency int64
|
||||
expected map[string]float64
|
||||
absent []string
|
||||
}{
|
||||
{
|
||||
name: "10 MHz",
|
||||
artifactDBFrequency: 10_000_000,
|
||||
configDBFrequency: 10_000_000,
|
||||
expected: map[string]float64{
|
||||
"windows_adfs_db_artifact_query_time_seconds_total": 0.25,
|
||||
"windows_adfs_db_artifact_queries_total": 5,
|
||||
"windows_adfs_db_config_query_time_seconds_total": 1.1521176,
|
||||
"windows_adfs_db_config_queries_total": 306,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "other frequency",
|
||||
artifactDBFrequency: 2_500_000,
|
||||
configDBFrequency: 3_000_000,
|
||||
expected: map[string]float64{
|
||||
"windows_adfs_db_artifact_query_time_seconds_total": 1,
|
||||
"windows_adfs_db_config_query_time_seconds_total": 3.840392,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unknown frequency",
|
||||
expected: map[string]float64{
|
||||
"windows_adfs_db_artifact_queries_total": 5,
|
||||
"windows_adfs_db_config_queries_total": 306,
|
||||
},
|
||||
absent: []string{
|
||||
"windows_adfs_db_artifact_query_time_seconds_total",
|
||||
"windows_adfs_db_config_query_time_seconds_total",
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
c := New(nil)
|
||||
c.buildDescriptors()
|
||||
|
||||
registry := prometheus.NewPedanticRegistry()
|
||||
require.NoError(t, registry.Register(metricCollector{
|
||||
collector: c,
|
||||
data: data,
|
||||
artifactDBFrequency: tc.artifactDBFrequency,
|
||||
configDBFrequency: tc.configDBFrequency,
|
||||
}))
|
||||
|
||||
families, err := registry.Gather()
|
||||
require.NoError(t, err)
|
||||
|
||||
byName := make(map[string]*dto.MetricFamily, len(families))
|
||||
for _, family := range families {
|
||||
byName[family.GetName()] = family
|
||||
}
|
||||
|
||||
for name, value := range tc.expected {
|
||||
family, ok := byName[name]
|
||||
require.True(t, ok, "missing metric %s", name)
|
||||
require.Equal(t, dto.MetricType_COUNTER, family.GetType(), name)
|
||||
require.Len(t, family.GetMetric(), 1, name)
|
||||
require.InDelta(t, value, family.GetMetric()[0].GetCounter().GetValue(), 1e-9, name)
|
||||
}
|
||||
|
||||
for _, name := range tc.absent {
|
||||
require.NotContains(t, byName, name)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type metricCollector struct {
|
||||
collector *Collector
|
||||
data perfDataCounterValues
|
||||
artifactDBFrequency int64
|
||||
configDBFrequency int64
|
||||
}
|
||||
|
||||
func (c metricCollector) Describe(ch chan<- *prometheus.Desc) {
|
||||
prometheus.DescribeByCollect(c, ch)
|
||||
}
|
||||
|
||||
func (c metricCollector) Collect(ch chan<- prometheus.Metric) {
|
||||
c.collector.collectMetrics(ch, c.data, c.artifactDBFrequency, c.configDBFrequency)
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/adfs"
|
||||
"github.com/prometheus-community/windows_exporter/internal/utils/testutils"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func BenchmarkCollector(b *testing.B) {
|
||||
@@ -29,5 +30,15 @@ func BenchmarkCollector(b *testing.B) {
|
||||
}
|
||||
|
||||
func TestCollector(t *testing.T) {
|
||||
testutils.TestCollector(t, adfs.New, nil)
|
||||
families := testutils.TestCollector(t, adfs.New, nil)
|
||||
|
||||
// The query times need the time base of their PERF_AVERAGE_TIMER counter.
|
||||
for _, name := range []string{
|
||||
"windows_adfs_db_artifact_queries_total",
|
||||
"windows_adfs_db_artifact_query_time_seconds_total",
|
||||
"windows_adfs_db_config_queries_total",
|
||||
"windows_adfs_db_config_query_time_seconds_total",
|
||||
} {
|
||||
require.Contains(t, families, name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,11 +17,20 @@
|
||||
|
||||
package adfs
|
||||
|
||||
// PERF_AVERAGE_TIMER counters. The raw value is the summed query time in ticks
|
||||
// of the counter's time base, the second value the number of queries.
|
||||
const (
|
||||
counterArtifactDBQueryTime = "Average Artifact Database Query Time"
|
||||
counterConfigDBQueryTime = "Average Config Database Query Time"
|
||||
)
|
||||
|
||||
type perfDataCounterValues struct {
|
||||
AdLoginConnectionFailures float64 `perfdata:"AD Login Connection Failures"`
|
||||
ArtifactDBFailures float64 `perfdata:"Artifact Database Connection Failures"`
|
||||
AvgArtifactDBQueryTime float64 `perfdata:"Average Artifact Database Query Time"`
|
||||
AvgConfigDBQueryTime float64 `perfdata:"Average Config Database Query Time"`
|
||||
ArtifactDBQueryTimeTicks float64 `perfdata:"Average Artifact Database Query Time"`
|
||||
ArtifactDBQueries float64 `perfdata:"Average Artifact Database Query Time,secondvalue"`
|
||||
ConfigDBQueryTimeTicks float64 `perfdata:"Average Config Database Query Time"`
|
||||
ConfigDBQueries float64 `perfdata:"Average Config Database Query Time,secondvalue"`
|
||||
CertificateAuthentications float64 `perfdata:"Certificate Authentications"`
|
||||
ConfigDBFailures float64 `perfdata:"Configuration Database Connection Failures"`
|
||||
DeviceAuthentications float64 `perfdata:"Device Authentications"`
|
||||
|
||||
@@ -345,12 +345,19 @@ func newCollector[T any](logger *slog.Logger, query *Query, resultType CounterTy
|
||||
counter.Type = counterInfo.DwType
|
||||
counter.MetricType = metricType(resultType, counter.Type)
|
||||
|
||||
if counter.Type == PERF_ELAPSED_TIME {
|
||||
switch counter.Type {
|
||||
case PERF_ELAPSED_TIME:
|
||||
if ret := GetCounterTimeBase(counterHandle, &counter.Frequency); ret != ErrorSuccess && ret != NoData {
|
||||
errs = append(errs, fmt.Errorf("GetCounterTimeBase: %w", NewPdhError(ret)))
|
||||
|
||||
continue
|
||||
}
|
||||
case PERF_AVERAGE_TIMER:
|
||||
// Only [Collector.Frequency] uses the time base of an average
|
||||
// timer, so a missing one is not an error. Collect retries it.
|
||||
if ret := GetCounterTimeBase(counterHandle, &counter.Frequency); ret != ErrorSuccess {
|
||||
counter.Frequency = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -440,6 +447,29 @@ func (c *Collector[T]) MetricType(counterName string) (prometheus.ValueType, boo
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// Frequency returns the time base in ticks per second of the raw timer counter
|
||||
// named counterName. The raw value of a PERF_AVERAGE_TIMER counter is the sum
|
||||
// of the measured durations in these ticks; divide it by the frequency to get
|
||||
// seconds. It returns false if the counter is unknown, is not such a timer or
|
||||
// its time base is not known yet. [Collector.Collect] reads a missing time
|
||||
// base again.
|
||||
func (c *Collector[T]) Frequency(counterName string) (int64, bool) {
|
||||
if c == nil {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
|
||||
for _, counter := range c.counters {
|
||||
if counter.Name == counterName {
|
||||
return counter.Frequency, counter.Frequency > 0
|
||||
}
|
||||
}
|
||||
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// Collect replaces the content of dst with one row per collected instance.
|
||||
func (c *Collector[T]) Collect(dst *[]T) error {
|
||||
if c == nil {
|
||||
@@ -540,12 +570,19 @@ func (c *Collector[T]) collect(dst *[]T, state *collectState) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// loadTimeBase reads the time base of a raw elapsed time counter that had none
|
||||
// when the collector was created. Early after a reboot, PDH can report no data
|
||||
// hasTimeBase reports whether the raw value of a counter of counterType is
|
||||
// measured in ticks of the counter's time base.
|
||||
func hasTimeBase(counterType uint32) bool {
|
||||
return counterType == PERF_ELAPSED_TIME || counterType == PERF_AVERAGE_TIMER
|
||||
}
|
||||
|
||||
// loadTimeBase reads the time base of a raw timer counter that had none when
|
||||
// the collector was created. Early after a reboot, PDH can report no data
|
||||
// for it, e.g. for APP_POOL_WAS while WAS is still starting (#2313). Without a
|
||||
// time base, the counter has no valid value and every instance is omitted.
|
||||
// time base, an elapsed time counter has no valid value and every instance is
|
||||
// omitted.
|
||||
func (c *Collector[T]) loadTimeBase(counter *Counter) {
|
||||
if c.resultType != CounterTypeRaw || counter.Type != PERF_ELAPSED_TIME || counter.Frequency > 0 {
|
||||
if c.resultType != CounterTypeRaw || !hasTimeBase(counter.Type) || counter.Frequency > 0 {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -397,6 +397,56 @@ func TestCollectLoadsMissingTimeBase(t *testing.T) {
|
||||
require.Positive(t, dst[0].SystemUpTime)
|
||||
}
|
||||
|
||||
// TestFrequencyOfAverageTimer checks that the time base of a raw average timer
|
||||
// is available, also when it was missing at startup, and that other counters
|
||||
// have none.
|
||||
func TestFrequencyOfAverageTimer(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type physicalDisk struct {
|
||||
AvgDiskSecPerRead float64 `perfdata:"Avg. Disk sec/Read"`
|
||||
AvgDiskSecPerReadBase float64 `perfdata:"Avg. Disk sec/Read,secondvalue"`
|
||||
DiskReadsPerSec float64 `perfdata:"Disk Reads/sec"`
|
||||
CurrentDiskQueueLength float64 `perfdata:"Current Disk Queue Length"`
|
||||
}
|
||||
|
||||
collector, err := NewCollector[physicalDisk](slog.New(slog.DiscardHandler), CounterTypeRaw, "PhysicalDisk", []string{"_Total"})
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Cleanup(collector.Close)
|
||||
|
||||
frequency, ok := collector.Frequency("Avg. Disk sec/Read")
|
||||
require.True(t, ok)
|
||||
require.Positive(t, frequency)
|
||||
|
||||
for _, name := range []string{"Disk Reads/sec", "Current Disk Queue Length", "unknown"} {
|
||||
_, ok = collector.Frequency(name)
|
||||
require.False(t, ok, name)
|
||||
}
|
||||
|
||||
// As if GetCounterTimeBase failed at startup.
|
||||
idx := slices.IndexFunc(collector.counters, func(counter Counter) bool { return counter.Name == "Avg. Disk sec/Read" })
|
||||
require.NotEqual(t, -1, idx)
|
||||
|
||||
collector.counters[idx].Frequency = 0
|
||||
|
||||
_, ok = collector.Frequency("Avg. Disk sec/Read")
|
||||
require.False(t, ok)
|
||||
|
||||
var dst []physicalDisk
|
||||
|
||||
require.NoError(t, collector.Collect(&dst))
|
||||
|
||||
reloaded, ok := collector.Frequency("Avg. Disk sec/Read")
|
||||
require.True(t, ok)
|
||||
require.Equal(t, frequency, reloaded)
|
||||
|
||||
var nilCollector *Collector[physicalDisk]
|
||||
|
||||
_, ok = nilCollector.Frequency("Avg. Disk sec/Read")
|
||||
require.False(t, ok)
|
||||
}
|
||||
|
||||
func TestMetricTypeForCounterType(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user