dhcp: query the distinct non-match denial counter (#2593)

Signed-off-by: Jan-Otto Kröpke <mail@jkroepke.de>
This commit is contained in:
Jan-Otto Kröpke
2026-10-09 11:21:12 +02:00
committed by GitHub
parent e34cf12826
commit a297fc8f19
3 changed files with 122 additions and 3 deletions
@@ -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())
}
+3 -2
View File
@@ -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()
}
+1 -1
View File
@@ -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."`