diff --git a/docs/collector.net.md b/docs/collector.net.md index ce8df5e6..6c523bf5 100644 --- a/docs/collector.net.md +++ b/docs/collector.net.md @@ -13,11 +13,11 @@ Enabled by default? | Yes ### `--collector.net.nic-include` -If given, an interface name needs to match the include regexp in order for the corresponding metrics to be reported +If given, an interface name needs to match the include regular expression in order for the corresponding metrics to be reported ### `--collector.net.nic-exclude` -If given, an interface name needs to *not* match the exclude regexp in order for the corresponding metrics to be reported +If given, an interface name needs to *not* match the exclude regular expression in order for the corresponding metrics to be reported ### `--collector.net.enabled` @@ -43,7 +43,8 @@ Comma-separated list of collectors to use. Defaults to all, if not specified. Su | `windows_net_nic_address_info` | A metric with a constant '1' value labeled with the network interface's address information. | gauge | `nic`, `address`, `family` | | `windows_net_nic_info` | A metric with a constant '1' value labeled with the network interface's general information. | gauge | `nic`, `friendly_name`, `mac` | | `windows_net_nic_operation_status` | The operational status for the interface as defined in RFC 2863 as IfOperStatus. | gauge | `nic`, `status` | -| `windows_net_route_info` | A metric with a constant '1' value labeled with the network interface's route information. | gauge | `nic`, `src`, `dest`, `metric` | + +Route information is not collected. The previously listed `windows_net_route_info` metric was never emitted and has been removed from the supported metric list. ### Example metric Query the rate of transmitted network traffic diff --git a/internal/collector/fsrmquota/fsrmquota.go b/internal/collector/fsrmquota/fsrmquota.go index 74e477bd..8d26e3f4 100644 --- a/internal/collector/fsrmquota/fsrmquota.go +++ b/internal/collector/fsrmquota/fsrmquota.go @@ -91,60 +91,7 @@ func (c *Collector) Build(_ *slog.Logger, miSession *mi.Session) error { c.miQuery = miQuery c.miSession = miSession - c.quotasCount = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "count"), - "Number of Quotas", - nil, - nil, - ) - c.peakUsage = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "peak_usage_bytes"), - "The highest amount of disk space usage charged to this quota. (PeakUsage)", - []string{"path", "template"}, - nil, - ) - c.size = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "size_bytes"), - "The size of the quota. (Size)", - []string{"path", "template"}, - nil, - ) - c.usage = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "usage_bytes"), - "The current amount of disk space usage charged to this quota. (Usage)", - []string{"path", "template"}, - nil, - ) - c.description = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "description"), - "Description of the quota (Description)", - []string{"path", "template", "description"}, - nil, - ) - c.disabled = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "disabled"), - "If 1, the quota is disabled. The default value is 0. (Disabled)", - []string{"path", "template"}, - nil, - ) - c.softLimit = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "softlimit"), - "If 1, the quota is a soft limit. If 0, the quota is a hard limit. The default value is 0. Optional (SoftLimit)", - []string{"path", "template"}, - nil, - ) - c.template = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "template"), - "Quota template name. (Template)", - []string{"path", "template"}, - nil, - ) - c.matchesTemplate = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "matchestemplate"), - "If 1, the property values of this quota match those values of the template from which it was derived. (MatchesTemplate)", - []string{"path", "template"}, - nil, - ) + c.buildDescriptors() var dst []msftFSRMQuota if err := c.miSession.Query(&dst, mi.NamespaceRootWindowsFSRM, c.miQuery, 0); err != nil { @@ -176,6 +123,12 @@ func (c *Collector) Collect(ch chan<- prometheus.Metric, maxScrapeDuration time. return fmt.Errorf("WMI query failed: %w", err) } + c.emitQuotas(ch, dst) + + return nil +} + +func (c *Collector) emitQuotas(ch chan<- prometheus.Metric, dst []msftFSRMQuota) { var count int for _, quota := range dst { @@ -184,6 +137,8 @@ func (c *Collector) Collect(ch chan<- prometheus.Metric, maxScrapeDuration time. template := quota.Template Description := quota.Description + ch <- prometheus.MustNewConstMetric(c.template, prometheus.GaugeValue, 1, path, template) + ch <- prometheus.MustNewConstMetric( c.peakUsage, prometheus.GaugeValue, @@ -245,6 +200,61 @@ func (c *Collector) Collect(ch chan<- prometheus.Metric, maxScrapeDuration time. prometheus.GaugeValue, float64(count), ) +} - return nil +func (c *Collector) buildDescriptors() { + c.quotasCount = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "count"), + "Number of Quotas", + nil, + nil, + ) + c.peakUsage = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "peak_usage_bytes"), + "The highest amount of disk space usage charged to this quota. (PeakUsage)", + []string{"path", "template"}, + nil, + ) + c.size = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "size_bytes"), + "The size of the quota. (Size)", + []string{"path", "template"}, + nil, + ) + c.usage = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "usage_bytes"), + "The current amount of disk space usage charged to this quota. (Usage)", + []string{"path", "template"}, + nil, + ) + c.description = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "description"), + "Description of the quota (Description)", + []string{"path", "template", "description"}, + nil, + ) + c.disabled = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "disabled"), + "If 1, the quota is disabled. The default value is 0. (Disabled)", + []string{"path", "template"}, + nil, + ) + c.softLimit = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "softlimit"), + "If 1, the quota is a soft limit. If 0, the quota is a hard limit. The default value is 0. Optional (SoftLimit)", + []string{"path", "template"}, + nil, + ) + c.template = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "template"), + "Quota template name. (Template)", + []string{"path", "template"}, + nil, + ) + c.matchesTemplate = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "matchestemplate"), + "If 1, the property values of this quota match those values of the template from which it was derived. (MatchesTemplate)", + []string{"path", "template"}, + nil, + ) } diff --git a/internal/collector/fsrmquota/template_metric_test.go b/internal/collector/fsrmquota/template_metric_test.go new file mode 100644 index 00000000..5ea8dde9 --- /dev/null +++ b/internal/collector/fsrmquota/template_metric_test.go @@ -0,0 +1,60 @@ +// 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 fsrmquota + +import ( + "testing" + + "github.com/prometheus/client_golang/prometheus" + dto "github.com/prometheus/client_model/go" + "github.com/stretchr/testify/require" +) + +func TestQuotaTemplateMetric(t *testing.T) { + t.Parallel() + + c := New(nil) + c.buildDescriptors() + + metrics := make(chan prometheus.Metric, 20) + c.emitQuotas(metrics, []msftFSRMQuota{{Path: "C:\\data", Template: "Standard"}}) + close(metrics) + + found := false + + for metric := range metrics { + if metric.Desc() != c.template { + continue + } + + var value dto.Metric + require.NoError(t, metric.Write(&value)) + require.InDelta(t, 1, value.GetGauge().GetValue(), 1e-9) + + labels := make(map[string]string) + for _, label := range value.GetLabel() { + labels[label.GetName()] = label.GetValue() + } + + require.Equal(t, map[string]string{"path": "C:\\data", "template": "Standard"}, labels) + + found = true + } + + require.True(t, found) +} diff --git a/internal/collector/hyperv/hyperv.go b/internal/collector/hyperv/hyperv.go index 180f3bee..4e7b7428 100644 --- a/internal/collector/hyperv/hyperv.go +++ b/internal/collector/hyperv/hyperv.go @@ -211,7 +211,11 @@ func (c *Collector) Build(logger *slog.Logger, miSession *mi.Session) error { subCollectorHypervisorRootPartition: { build: c.buildHypervisorRootPartition, collect: c.collectHypervisorRootPartition, - close: func() { c.perfDataCollectorHypervisorRootPartition.Close() }, + close: func() { + if c.perfDataCollectorHypervisorRootPartition != nil { + c.perfDataCollectorHypervisorRootPartition.Close() + } + }, }, subCollectorHypervisorRootVirtualProcessor: { build: c.buildHypervisorRootVirtualProcessor, diff --git a/internal/collector/hyperv/hyperv_hypervisor_root_partition.go b/internal/collector/hyperv/hyperv_hypervisor_root_partition.go index 61b44c24..fc6087bb 100644 --- a/internal/collector/hyperv/hyperv_hypervisor_root_partition.go +++ b/internal/collector/hyperv/hyperv_hypervisor_root_partition.go @@ -21,13 +21,14 @@ import ( "fmt" "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" ) // collectorHypervisorRootPartition Hyper-V Hypervisor Root Partition metrics type collectorHypervisorRootPartition struct { - perfDataCollectorHypervisorRootPartition *pdh.Collector[perfDataCounterValuesHypervisorRootPartition] + perfDataCollectorHypervisorRootPartition pdhtypes.Collector[perfDataCounterValuesHypervisorRootPartition] perfDataObjectHypervisorRootPartition []perfDataCounterValuesHypervisorRootPartition hypervisorRootPartitionAddressSpaces *prometheus.Desc // \Hyper-V Hypervisor Root Partition(*)\Address Spaces @@ -85,6 +86,12 @@ func (c *Collector) buildHypervisorRootPartition() error { return fmt.Errorf("failed to create Hyper-V Hypervisor Root Partition collector: %w", err) } + c.buildHypervisorRootPartitionDescriptors() + + return nil +} + +func (c *Collector) buildHypervisorRootPartitionDescriptors() { c.hypervisorRootPartitionAddressSpaces = prometheus.NewDesc( prometheus.BuildFQName(types.Namespace, Name, "root_partition_address_spaces"), "The number of address spaces in the virtual TLB of the partition", @@ -211,8 +218,6 @@ func (c *Collector) buildHypervisorRootPartition() error { nil, nil, ) - - return nil } func (c *Collector) collectHypervisorRootPartition(ch chan<- prometheus.Metric) error { @@ -251,6 +256,12 @@ func (c *Collector) collectHypervisorRootPartition(ch chan<- prometheus.Metric) c.perfDataObjectHypervisorRootPartition[0].HypervisorRootPartitionDeviceInterruptErrors, ) + ch <- prometheus.MustNewConstMetric( + c.hypervisorRootPartitionDeviceInterruptMappings, + prometheus.GaugeValue, + c.perfDataObjectHypervisorRootPartition[0].HypervisorRootPartitionDeviceInterruptMappings, + ) + ch <- prometheus.MustNewConstMetric( c.hypervisorRootPartitionDeviceInterruptThrottleEvents, prometheus.GaugeValue, diff --git a/internal/collector/hyperv/interrupt_mappings_test.go b/internal/collector/hyperv/interrupt_mappings_test.go new file mode 100644 index 00000000..ba368f7f --- /dev/null +++ b/internal/collector/hyperv/interrupt_mappings_test.go @@ -0,0 +1,62 @@ +// 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 hyperv + +import ( + "testing" + + "github.com/prometheus/client_golang/prometheus" + dto "github.com/prometheus/client_model/go" + "github.com/stretchr/testify/require" +) + +func TestRootPartitionInterruptMappings(t *testing.T) { + t.Parallel() + + c := New(nil) + c.buildHypervisorRootPartitionDescriptors() + c.perfDataCollectorHypervisorRootPartition = interruptMappingFixture{} + metrics := make(chan prometheus.Metric, 100) + require.NoError(t, c.collectHypervisorRootPartition(metrics)) + close(metrics) + + found := false + + for metric := range metrics { + if metric.Desc() != c.hypervisorRootPartitionDeviceInterruptMappings { + continue + } + + var value dto.Metric + require.NoError(t, metric.Write(&value)) + require.InDelta(t, 73, value.GetGauge().GetValue(), 1e-9) + + found = true + } + + require.True(t, found) +} + +type interruptMappingFixture struct{} + +func (interruptMappingFixture) Collect(dst *[]perfDataCounterValuesHypervisorRootPartition) error { + *dst = []perfDataCounterValuesHypervisorRootPartition{{HypervisorRootPartitionDeviceInterruptMappings: 73}} + + return nil +} +func (interruptMappingFixture) Close() {} diff --git a/internal/collector/logical_disk/logical_disk.go b/internal/collector/logical_disk/logical_disk.go index 2823c5cb..d3d32084 100644 --- a/internal/collector/logical_disk/logical_disk.go +++ b/internal/collector/logical_disk/logical_disk.go @@ -34,6 +34,7 @@ import ( "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/pdh" "github.com/prometheus-community/windows_exporter/internal/types" + "github.com/prometheus-community/windows_exporter/internal/utils" "github.com/prometheus/client_golang/prometheus" "golang.org/x/sys/windows" ) @@ -381,6 +382,10 @@ func (c *Collector) Collect(ch chan<- prometheus.Metric, _ time.Duration) error ) } + if err == nil { + ch <- prometheus.MustNewConstMetric(c.readOnly, prometheus.GaugeValue, info.readonly, data.Name) + } + ch <- prometheus.MustNewConstMetric( c.information, prometheus.GaugeValue, @@ -714,7 +719,7 @@ func getVolumeInfo(volumes map[string]string, rootDrive string) (volumeInfo, err label: windows.UTF16PtrToString(&volBufLabel[0]), filesystem: windows.UTF16PtrToString(&volBufType[0]), serialNumber: fmt.Sprintf("%X", volSerialNum), - readonly: float64(fsFlags & windows.FILE_READ_ONLY_VOLUME), + readonly: utils.BoolToFloat(fsFlags&windows.FILE_READ_ONLY_VOLUME != 0), }, nil } diff --git a/internal/collector/logical_disk/logical_disk_test.go b/internal/collector/logical_disk/logical_disk_test.go index 37b611b9..34b64c60 100644 --- a/internal/collector/logical_disk/logical_disk_test.go +++ b/internal/collector/logical_disk/logical_disk_test.go @@ -134,3 +134,20 @@ func TestCollectorVolumeFilters(t *testing.T) { }) } } + +func TestCollectorReadOnlyMetric(t *testing.T) { + metrics := testutils.TestCollector(t, logical_disk.New, &logical_disk.Config{ + CollectorsEnabled: logical_disk.ConfigDefaults.CollectorsEnabled, + VolumeInclude: types.RegExpAny, + }) + family := metrics["windows_logical_disk_readonly"] + require.NotNil(t, family) + require.NotEmpty(t, family.GetMetric()) + + for _, metric := range family.GetMetric() { + value := metric.GetGauge().GetValue() + require.True(t, value == 0 || value == 1, "read-only flag must be normalized to a boolean") + require.Len(t, metric.GetLabel(), 1) + require.Equal(t, "volume", metric.GetLabel()[0].GetName()) + } +} diff --git a/internal/collector/net/net.go b/internal/collector/net/net.go index 33f2a914..e65b91ed 100644 --- a/internal/collector/net/net.go +++ b/internal/collector/net/net.go @@ -83,7 +83,6 @@ type Collector struct { nicIPAddressInfo *prometheus.Desc nicOperStatus *prometheus.Desc nicInfo *prometheus.Desc - routeInfo *prometheus.Desc } func New(config *Config) *Collector { @@ -271,12 +270,6 @@ func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error { []string{"nic", "friendly_name", "mac"}, nil, ) - c.routeInfo = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "route_info"), - "A metric with a constant '1' value labeled with the network interface's route information.", - []string{"nic", "src", "dest", "metric"}, - nil, - ) var err error diff --git a/internal/collector/smtp/general_failure_test.go b/internal/collector/smtp/general_failure_test.go new file mode 100644 index 00000000..249f23dc --- /dev/null +++ b/internal/collector/smtp/general_failure_test.go @@ -0,0 +1,74 @@ +// 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 smtp + +import ( + "log/slog" + "testing" + + "github.com/prometheus/client_golang/prometheus" + dto "github.com/prometheus/client_model/go" + "github.com/stretchr/testify/require" +) + +func TestGeneralFailureMetric(t *testing.T) { + t.Parallel() + + c := New(nil) + // Descriptors are initialized before connecting to the optional SMTP provider. + _ = c.Build(slog.New(slog.DiscardHandler), nil) + if c.perfDataCollector != nil { + c.perfDataCollector.Close() + } + + c.perfDataCollector = generalFailureFixture{} + metrics := make(chan prometheus.Metric, 100) + require.NoError(t, c.Collect(metrics, 0)) + close(metrics) + + found := false + + for metric := range metrics { + if metric.Desc() != c.badMailedMessagesGeneralFailureTotal { + continue + } + + var value dto.Metric + require.NoError(t, metric.Write(&value)) + require.InDelta(t, 42, value.GetCounter().GetValue(), 1e-9) + require.Equal(t, "mail", value.GetLabel()[0].GetValue()) + + found = true + } + + require.True(t, found) +} + +type generalFailureFixture struct{} + +func (generalFailureFixture) Collect(dst *[]perfDataCounterValues) error { + *dst = []perfDataCounterValues{{Name: "mail", BadmailedMessagesGeneralFailureTotal: 42}} + + return nil +} +func (generalFailureFixture) Close() {} + +func TestCloseBeforeProviderInitialization(t *testing.T) { + t.Parallel() + require.NoError(t, New(nil).Close()) +} diff --git a/internal/collector/smtp/smtp.go b/internal/collector/smtp/smtp.go index 004b01aa..2bb36004 100644 --- a/internal/collector/smtp/smtp.go +++ b/internal/collector/smtp/smtp.go @@ -26,6 +26,7 @@ import ( "github.com/alecthomas/kingpin/v2" "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" ) @@ -46,7 +47,7 @@ var ConfigDefaults = Config{ type Collector struct { config Config - perfDataCollector *pdh.Collector[perfDataCounterValues] + perfDataCollector pdhtypes.Collector[perfDataCounterValues] perfDataObject []perfDataCounterValues badMailedMessagesBadPickupFileTotal *prometheus.Desc @@ -154,7 +155,9 @@ func (c *Collector) GetName() string { } func (c *Collector) Close() error { - c.perfDataCollector.Close() + if c.perfDataCollector != nil { + c.perfDataCollector.Close() + } return nil } @@ -448,6 +451,13 @@ func (c *Collector) Collect(ch chan<- prometheus.Metric, _ time.Duration) error data.Name, ) + ch <- prometheus.MustNewConstMetric( + c.badMailedMessagesGeneralFailureTotal, + prometheus.CounterValue, + data.BadmailedMessagesGeneralFailureTotal, + data.Name, + ) + ch <- prometheus.MustNewConstMetric( c.badMailedMessagesHopCountExceededTotal, prometheus.CounterValue,