diff --git a/internal/collector/dhcp/denial_counters_test.go b/internal/collector/dhcp/denial_counters_test.go new file mode 100644 index 00000000..86b85117 --- /dev/null +++ b/internal/collector/dhcp/denial_counters_test.go @@ -0,0 +1,118 @@ +// 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 dhcp + +import ( + "errors" + "log/slog" + "reflect" + "testing" + + "github.com/prometheus-community/windows_exporter/internal/pdh" + "github.com/prometheus/client_golang/prometheus" + dto "github.com/prometheus/client_model/go" + "github.com/stretchr/testify/require" +) + +func TestDenialCounterValues(t *testing.T) { + t.Parallel() + + c := New(&Config{CollectorsEnabled: []string{subCollectorServerMetrics}}) + // Build creates descriptors before attempting to connect to the optional DHCP provider. + _ = c.Build(slog.New(slog.DiscardHandler), nil) + if c.perfDataCollector != nil { + c.perfDataCollector.Close() + } + + c.perfDataCollector = denialFixture{ + "Denied due to match.": 7, + "Denied due to nonmatch.": 11, + } + metrics := make(chan prometheus.Metric, 100) + require.NoError(t, c.collectServerMetrics(metrics)) + close(metrics) + + found := make(map[*prometheus.Desc]float64) + + for metric := range metrics { + var value dto.Metric + require.NoError(t, metric.Write(&value)) + found[metric.Desc()] = value.GetCounter().GetValue() + } + + require.InDelta(t, 7, found[c.deniedDueToMatch], 1e-9) + require.InDelta(t, 11, found[c.deniedDueToNonMatch], 1e-9) +} + +type denialFixture map[string]float64 + +func (f denialFixture) Collect(dst *[]perfDataCounterValues) error { + var row perfDataCounterValues + + value := reflect.ValueOf(&row).Elem() + for _, field := range reflect.VisibleFields(value.Type()) { + value.FieldByIndex(field.Index).SetFloat(f[field.Tag.Get("perfdata")]) + } + + *dst = []perfDataCounterValues{row} + + return nil +} + +func (f denialFixture) Close() {} + +func TestNativeDenialCounterNames(t *testing.T) { + t.Parallel() + + logger := slog.New(slog.DiscardHandler) + // Probe a known counter first so absence of the DHCP role can be skipped. + probe, err := pdh.NewDynamicCollector(logger, pdh.CounterTypeRaw, "DHCP Server", nil, []string{"Acks/sec"}) + if probe != nil { + t.Cleanup(probe.Close) + } + + if errors.Is(err, pdh.NewPdhError(pdh.CstatusNoObject)) || errors.Is(err, pdh.NewPdhError(pdh.CstatusNoCounter)) { + t.Skip("DHCP Server performance counters are unavailable") + } + + require.NoError(t, err) + + names := make([]string, 0, 2) + + for _, fieldName := range []string{"DeniedDueToMatch", "DeniedDueToNonMatch"} { + field, ok := reflect.TypeFor[perfDataCounterValues]().FieldByName(fieldName) + require.True(t, ok) + + names = append(names, field.Tag.Get("perfdata")) + } + + native, err := pdh.NewDynamicCollector(logger, pdh.CounterTypeRaw, "DHCP Server", nil, names) + if native != nil { + t.Cleanup(native.Close) + } + + require.NoError(t, err, "the production denial counter names must exist on the native provider") +} + +func TestCloseAfterInvalidSubcollectorBuild(t *testing.T) { + t.Parallel() + + c := New(&Config{CollectorsEnabled: []string{subCollectorServerMetrics, "invalid"}}) + require.Error(t, c.Build(slog.New(slog.DiscardHandler), nil)) + require.NoError(t, c.Close()) +} diff --git a/internal/collector/dhcp/dhcp.go b/internal/collector/dhcp/dhcp.go index 93fa1154..98af4942 100644 --- a/internal/collector/dhcp/dhcp.go +++ b/internal/collector/dhcp/dhcp.go @@ -30,6 +30,7 @@ import ( "github.com/prometheus-community/windows_exporter/internal/headers/dhcpsapi" "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/pdh" + pdhtypes "github.com/prometheus-community/windows_exporter/internal/pdh/types" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus/client_golang/prometheus" ) @@ -59,7 +60,7 @@ type Collector struct { logger *slog.Logger - perfDataCollector *pdh.Collector[perfDataCounterValues] + perfDataCollector pdhtypes.Collector[perfDataCounterValues] perfDataObject []perfDataCounterValues acksTotal *prometheus.Desc @@ -143,7 +144,7 @@ func (c *Collector) GetName() string { } func (c *Collector) Close() error { - if slices.Contains(c.config.CollectorsEnabled, subCollectorServerMetrics) { + if slices.Contains(c.config.CollectorsEnabled, subCollectorServerMetrics) && c.perfDataCollector != nil { c.perfDataCollector.Close() } diff --git a/internal/collector/dhcp/types.go b/internal/collector/dhcp/types.go index f56a80fc..70351d9b 100644 --- a/internal/collector/dhcp/types.go +++ b/internal/collector/dhcp/types.go @@ -23,7 +23,7 @@ type perfDataCounterValues struct { ConflictCheckQueueLength float64 `perfdata:"Conflict Check Queue Length"` DeclinesTotal float64 `perfdata:"Declines/sec"` DeniedDueToMatch float64 `perfdata:"Denied due to match."` - DeniedDueToNonMatch float64 `perfdata:"Denied due to match."` + DeniedDueToNonMatch float64 `perfdata:"Denied due to nonmatch."` DiscoversTotal float64 `perfdata:"Discovers/sec"` DuplicatesDroppedTotal float64 `perfdata:"Duplicates Dropped/sec"` FailoverBndAckReceivedTotal float64 `perfdata:"Failover: BndAck received/sec."`