1616package dns
1717
1818import (
19+ "errors"
1920 "fmt"
2021 "log/slog"
22+ "slices"
23+ "strings"
2124
2225 "github.com/alecthomas/kingpin/v2"
2326 "github.com/prometheus-community/windows_exporter/internal/mi"
@@ -26,12 +29,23 @@ import (
2629 "github.com/prometheus/client_golang/prometheus"
2730)
2831
29- const Name = "dns"
32+ const (
33+ Name = "dns"
34+ subCollectorMetrics = "metrics"
35+ subCollectorWMIStats = "wmi_stats"
36+ )
3037
31- type Config struct {}
38+ type Config struct {
39+ CollectorsEnabled []string `yaml:"collectors_enabled"`
40+ }
3241
3342//nolint:gochecknoglobals
34- var ConfigDefaults = Config {}
43+ var ConfigDefaults = Config {
44+ CollectorsEnabled : []string {
45+ subCollectorMetrics ,
46+ subCollectorWMIStats ,
47+ },
48+ }
3549
3650// A Collector is a Prometheus Collector for WMI Win32_PerfRawData_DNS_DNS metrics.
3751type Collector struct {
@@ -40,6 +54,9 @@ type Collector struct {
4054 perfDataCollector * pdh.Collector
4155 perfDataObject []perfDataCounterValues
4256
57+ miSession * mi.Session
58+ miQuery mi.Query
59+
4360 dynamicUpdatesFailures * prometheus.Desc
4461 dynamicUpdatesQueued * prometheus.Desc
4562 dynamicUpdatesReceived * prometheus.Desc
@@ -62,22 +79,45 @@ type Collector struct {
6279 zoneTransferResponsesReceived * prometheus.Desc
6380 zoneTransferSuccessReceived * prometheus.Desc
6481 zoneTransferSuccessSent * prometheus.Desc
82+ dnsWMIStats * prometheus.Desc
6583}
6684
6785func New (config * Config ) * Collector {
6886 if config == nil {
6987 config = & ConfigDefaults
7088 }
7189
90+ if config .CollectorsEnabled == nil {
91+ config .CollectorsEnabled = ConfigDefaults .CollectorsEnabled
92+ }
93+
7294 c := & Collector {
7395 config : * config ,
7496 }
7597
7698 return c
7799}
78100
79- func NewWithFlags (_ * kingpin.Application ) * Collector {
80- return & Collector {}
101+ func NewWithFlags (app * kingpin.Application ) * Collector {
102+ c := & Collector {
103+ config : ConfigDefaults ,
104+ }
105+ c .config .CollectorsEnabled = make ([]string , 0 )
106+
107+ var collectorsEnabled string
108+
109+ app .Flag (
110+ "collector.dns.enabled" ,
111+ "Comma-separated list of collectors to use. Defaults to all, if not specified." ,
112+ ).Default (strings .Join (ConfigDefaults .CollectorsEnabled , "," )).StringVar (& collectorsEnabled )
113+
114+ app .Action (func (* kingpin.ParseContext ) error {
115+ c .config .CollectorsEnabled = strings .Split (collectorsEnabled , "," )
116+
117+ return nil
118+ })
119+
120+ return c
81121}
82122
83123func (c * Collector ) GetName () string {
@@ -90,7 +130,31 @@ func (c *Collector) Close() error {
90130 return nil
91131}
92132
93- func (c * Collector ) Build (_ * slog.Logger , _ * mi.Session ) error {
133+ func (c * Collector ) Build (_ * slog.Logger , miSession * mi.Session ) error {
134+ for _ , collector := range c .config .CollectorsEnabled {
135+ if ! slices .Contains ([]string {subCollectorMetrics , subCollectorWMIStats }, collector ) {
136+ return fmt .Errorf ("unknown sub collector: %s. Possible values: %s" , collector ,
137+ strings .Join ([]string {subCollectorMetrics , subCollectorWMIStats }, ", " ),
138+ )
139+ }
140+ }
141+
142+ if slices .Contains (c .config .CollectorsEnabled , subCollectorMetrics ) {
143+ if err := c .buildMetricsCollector (); err != nil {
144+ return err
145+ }
146+ }
147+
148+ if slices .Contains (c .config .CollectorsEnabled , subCollectorWMIStats ) {
149+ if err := c .buildErrorStatsCollector (miSession ); err != nil {
150+ return err
151+ }
152+ }
153+
154+ return nil
155+ }
156+
157+ func (c * Collector ) buildMetricsCollector () error {
94158 c .zoneTransferRequestsReceived = prometheus .NewDesc (
95159 prometheus .BuildFQName (types .Namespace , Name , "zone_transfer_requests_received_total" ),
96160 "Number of zone transfer requests (AXFR/IXFR) received by the master DNS server" ,
@@ -224,6 +288,13 @@ func (c *Collector) Build(_ *slog.Logger, _ *mi.Session) error {
224288 nil ,
225289 )
226290
291+ c .dnsWMIStats = prometheus .NewDesc (
292+ prometheus .BuildFQName (types .Namespace , Name , "wmi_stats_total" ),
293+ "DNS WMI statistics from MicrosoftDNS_Statistic" ,
294+ []string {"name" , "collection_name" , "dns_server" },
295+ nil ,
296+ )
297+
227298 var err error
228299
229300 c .perfDataCollector , err = pdh .NewCollector [perfDataCounterValues ](pdh .CounterTypeRaw , "DNS" , pdh .InstancesAll )
@@ -234,9 +305,43 @@ func (c *Collector) Build(_ *slog.Logger, _ *mi.Session) error {
234305 return nil
235306}
236307
308+ func (c * Collector ) buildErrorStatsCollector (miSession * mi.Session ) error {
309+ if miSession == nil {
310+ return errors .New ("miSession is nil" )
311+ }
312+
313+ query , err := mi .NewQuery ("SELECT Name, CollectionName, Value, DnsServerName FROM MicrosoftDNS_Statistic WHERE CollectionName = 'Error Stats'" )
314+ if err != nil {
315+ return fmt .Errorf ("failed to create query: %w" , err )
316+ }
317+
318+ c .miSession = miSession
319+ c .miQuery = query
320+
321+ return nil
322+ }
323+
237324// Collect sends the metric values for each metric
238325// to the provided prometheus Metric channel.
239326func (c * Collector ) Collect (ch chan <- prometheus.Metric ) error {
327+ errs := make ([]error , 0 )
328+
329+ if slices .Contains (c .config .CollectorsEnabled , subCollectorMetrics ) {
330+ if err := c .collectMetrics (ch ); err != nil {
331+ errs = append (errs , fmt .Errorf ("failed collecting metrics: %w" , err ))
332+ }
333+ }
334+
335+ if slices .Contains (c .config .CollectorsEnabled , subCollectorWMIStats ) {
336+ if err := c .collectErrorStats (ch ); err != nil {
337+ errs = append (errs , fmt .Errorf ("failed collecting WMI statistics: %w" , err ))
338+ }
339+ }
340+
341+ return errors .Join (errs ... )
342+ }
343+
344+ func (c * Collector ) collectMetrics (ch chan <- prometheus.Metric ) error {
240345 err := c .perfDataCollector .Collect (& c .perfDataObject )
241346 if err != nil {
242347 return fmt .Errorf ("failed to collect DNS metrics: %w" , err )
@@ -493,3 +598,24 @@ func (c *Collector) Collect(ch chan<- prometheus.Metric) error {
493598
494599 return nil
495600}
601+
602+ func (c * Collector ) collectErrorStats (ch chan <- prometheus.Metric ) error {
603+ var stats []Statistic
604+ if err := c .miSession .Query (& stats , mi .NamespaceRootMicrosoftDNS , c .miQuery ); err != nil {
605+ return fmt .Errorf ("failed to query DNS statistics: %w" , err )
606+ }
607+
608+ // Collect DNS error statistics
609+ for _ , stat := range stats {
610+ ch <- prometheus .MustNewConstMetric (
611+ c .dnsWMIStats ,
612+ prometheus .CounterValue ,
613+ float64 (stat .Value ),
614+ stat .Name ,
615+ stat .CollectionName ,
616+ stat .DnsServerName ,
617+ )
618+ }
619+
620+ return nil
621+ }
0 commit comments