From 7a9a4e5831458a02ddfd9c05380b2a0903db10e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-Otto=20Kr=C3=B6pke?= Date: Thu, 14 Nov 2024 22:39:59 +0100 Subject: [PATCH] pagefile: BREAKING: move paging metrics from os to dedicated collector (click PR for more information) (#1735) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jan-Otto Kröpke --- README.md | 1 + docs/collector.os.md | 12 +- docs/collector.pagefile.md | 38 ++++++ internal/collector/os/os.go | 15 +- internal/collector/pagefile/const.go | 5 + internal/collector/pagefile/pagefile.go | 136 +++++++++++++++++++ internal/collector/pagefile/pagefile_test.go | 16 +++ internal/perfdata/v2/collector.go | 19 +-- pkg/collector/collector.go | 2 + pkg/collector/config.go | 3 + pkg/collector/map.go | 2 + tools/e2e-output.txt | 10 +- tools/end-to-end-test.ps1 | 4 +- 13 files changed, 238 insertions(+), 25 deletions(-) create mode 100644 docs/collector.pagefile.md create mode 100644 internal/collector/pagefile/const.go create mode 100644 internal/collector/pagefile/pagefile.go create mode 100644 internal/collector/pagefile/pagefile_test.go diff --git a/README.md b/README.md index 7cbdbf8b..a0c4036c 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ Name | Description | Enabled by default [netframework](docs/collector.netframework.md) | .NET Framework metrics | [net](docs/collector.net.md) | Network interface I/O | ✓ [os](docs/collector.os.md) | OS metrics (memory, processes, users) | ✓ +[pagefile](docs/collector.pagefile.md) | pagefile metrics | [perfdata](docs/collector.perfdata.md) | Custom perfdata metrics | [physical_disk](docs/collector.physical_disk.md) | physical disk metrics | ✓ [printer](docs/collector.printer.md) | Printer metrics | diff --git a/docs/collector.os.md b/docs/collector.os.md index 34b3ab0d..a02c7686 100644 --- a/docs/collector.os.md +++ b/docs/collector.os.md @@ -14,13 +14,11 @@ None ## Metrics -| Name | Description | Type | Labels | -|---------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------|------------------------------------------------------------------------| -| `windows_os_info` | Contains full product name & version in labels. Note that the `major_version` for Windows 11 is "10"; a build number greater than 22000 represents Windows 11. | gauge | `product`, `version`, `major_version`, `minor_version`, `build_number` | -| `windows_os_paging_limit_bytes` | Total number of bytes that can be stored in the operating system paging files. 0 (zero) indicates that there are no paging files | gauge | None | -| `windows_os_paging_free_bytes` | Number of bytes that can be mapped into the operating system paging files without causing any other pages to be swapped out | gauge | None | +| Name | Description | Type | Labels | +|-----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|-------|------------------------------------------------------------------------| +| `windows_os_hostname` | Labelled system hostname information as provided by ComputerSystem.DNSHostName and ComputerSystem.Domain | gauge | `domain`, `fqdn`, `hostname` | +| `windows_os_info` | Contains full product name & version in labels. Note that the `major_version` for Windows 11 is "10"; a build number greater than 22000 represents Windows 11. | gauge | `product`, `version`, `major_version`, `minor_version`, `build_number` | - ### Example metric ``` @@ -36,4 +34,4 @@ windows_os_info{build_number="19045",major_version="10",minor_version="0",produc _This collector does not yet have useful queries, we would appreciate your help adding them!_ ## Alerting examples -_This collector does not yet have alerting examples, we would appreciate your help adding them!_ \ No newline at end of file +_This collector does not yet have alerting examples, we would appreciate your help adding them!_ diff --git a/docs/collector.pagefile.md b/docs/collector.pagefile.md new file mode 100644 index 00000000..367e94cb --- /dev/null +++ b/docs/collector.pagefile.md @@ -0,0 +1,38 @@ +# pagefile collector + +The pagefile collector exposes metrics about the pagefile usage + +||| +-|- +Metric name prefix | `pagefile` +Classes | [`Win32_OperatingSystem`](https://msdn.microsoft.com/en-us/library/aa394239) +Enabled by default? | Yes + +## Flags + +None + +## Metrics + +| Name | Description | Type | Labels | +|--------------------------------|-----------------------------------------------------------------------------------------------------------------------------|-------|--------| +| `windows_pagefile_free_bytes` | Number of bytes that can be mapped into the operating system paging files without causing any other pages to be swapped out | gauge | `file` | +| `windows_pagefile_limit_bytes` | Number of bytes that can be stored in the operating system paging files. 0 (zero) indicates that there are no paging files | gauge | `file` | + + +### Example metric + +``` +# HELP windows_pagefile_free_bytes OperatingSystem.FreeSpaceInPagingFiles +# TYPE windows_pagefile_free_bytes gauge +windows_pagefile_free_bytes{file="C:\\pagefile.sys"} 6.025797632e+09 +# HELP windows_pagefile_limit_bytes OperatingSystem.SizeStoredInPagingFiles +# TYPE windows_pagefile_limit_bytes gauge +windows_pagefile_limit_bytes{file="C:\\pagefile.sys"} 6.442450944e+09 +``` + +## Useful queries +_This collector does not yet have useful queries, we would appreciate your help adding them!_ + +## Alerting examples +_This collector does not yet have alerting examples, we would appreciate your help adding them!_ diff --git a/internal/collector/os/os.go b/internal/collector/os/os.go index 4920cecf..77ec9acf 100644 --- a/internal/collector/os/os.go +++ b/internal/collector/os/os.go @@ -34,9 +34,14 @@ var ConfigDefaults = Config{} type Collector struct { config Config - hostname *prometheus.Desc - osInformation *prometheus.Desc - pagingFreeBytes *prometheus.Desc + hostname *prometheus.Desc + osInformation *prometheus.Desc + + // pagingFreeBytes + // Deprecated: Use windows_paging_free_bytes instead. + pagingFreeBytes *prometheus.Desc + // pagingLimitBytes + // Deprecated: Use windows_paging_total_bytes instead. pagingLimitBytes *prometheus.Desc // users @@ -151,13 +156,13 @@ func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error { ) c.pagingLimitBytes = prometheus.NewDesc( prometheus.BuildFQName(types.Namespace, Name, "paging_limit_bytes"), - "OperatingSystem.SizeStoredInPagingFiles", + "Deprecated: Use windows_pagefile_limit_bytes instead.", nil, nil, ) c.pagingFreeBytes = prometheus.NewDesc( prometheus.BuildFQName(types.Namespace, Name, "paging_free_bytes"), - "OperatingSystem.FreeSpaceInPagingFiles", + "Deprecated: Use windows_pagefile_free_bytes instead.", nil, nil, ) diff --git a/internal/collector/pagefile/const.go b/internal/collector/pagefile/const.go new file mode 100644 index 00000000..0eaf9e98 --- /dev/null +++ b/internal/collector/pagefile/const.go @@ -0,0 +1,5 @@ +package pagefile + +const ( + usage = "% Usage" +) diff --git a/internal/collector/pagefile/pagefile.go b/internal/collector/pagefile/pagefile.go new file mode 100644 index 00000000..41d4f022 --- /dev/null +++ b/internal/collector/pagefile/pagefile.go @@ -0,0 +1,136 @@ +//go:build windows + +package pagefile + +import ( + "fmt" + "log/slog" + "os" + "strings" + + "github.com/alecthomas/kingpin/v2" + "github.com/prometheus-community/windows_exporter/internal/headers/psapi" + "github.com/prometheus-community/windows_exporter/internal/mi" + "github.com/prometheus-community/windows_exporter/internal/perfdata" + "github.com/prometheus-community/windows_exporter/internal/types" + "github.com/prometheus/client_golang/prometheus" +) + +const Name = "pagefile" + +type Config struct{} + +var ConfigDefaults = Config{} + +// A Collector is a Prometheus Collector for WMI metrics. +type Collector struct { + config Config + + perfDataCollector perfdata.Collector + + pagingFreeBytes *prometheus.Desc + pagingLimitBytes *prometheus.Desc +} + +func New(config *Config) *Collector { + if config == nil { + config = &ConfigDefaults + } + + c := &Collector{ + config: *config, + } + + return c +} + +func NewWithFlags(_ *kingpin.Application) *Collector { + return &Collector{} +} + +func (c *Collector) GetName() string { + return Name +} + +func (c *Collector) GetPerfCounter(_ *slog.Logger) ([]string, error) { + return []string{}, nil +} + +func (c *Collector) Close(_ *slog.Logger) error { + return nil +} + +func (c *Collector) Build(_ *slog.Logger, _ *mi.Session) error { + counters := []string{ + usage, + } + + var err error + + c.perfDataCollector, err = perfdata.NewCollector(perfdata.V2, "Paging File", perfdata.AllInstances, counters) + if err != nil { + return fmt.Errorf("failed to create Paging File collector: %w", err) + } + + c.pagingLimitBytes = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "limit_bytes"), + "Number of bytes that can be stored in the operating system paging files. 0 (zero) indicates that there are no paging files", + []string{"file"}, + nil, + ) + + c.pagingFreeBytes = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "free_bytes"), + "Number of bytes that can be mapped into the operating system paging files without causing any other pages to be swapped out", + []string{"file"}, + nil, + ) + + return nil +} + +// Collect sends the metric values for each metric +// to the provided prometheus Metric channel. +func (c *Collector) Collect(_ *types.ScrapeContext, _ *slog.Logger, ch chan<- prometheus.Metric) error { + return c.collectPaging(ch) +} + +func (c *Collector) collectPaging(ch chan<- prometheus.Metric) error { + data, err := c.perfDataCollector.Collect() + if err != nil { + return fmt.Errorf("failed to collect Paging File metrics: %w", err) + } + + gpi, err := psapi.GetPerformanceInfo() + if err != nil { + return err + } + + for fileName, pageFile := range data { + fileString := strings.ReplaceAll(fileName, `\??\`, "") + file, err := os.Stat(fileString) + + var fileSize float64 + + // For unknown reasons, Windows doesn't always create a page file. Continue collection rather than aborting. + if err == nil { + fileSize = float64(file.Size()) + } + + ch <- prometheus.MustNewConstMetric( + c.pagingFreeBytes, + prometheus.GaugeValue, + fileSize-(pageFile[usage].FirstValue*float64(gpi.PageSize)), + fileString, + ) + + ch <- prometheus.MustNewConstMetric( + c.pagingLimitBytes, + prometheus.GaugeValue, + fileSize, + fileString, + ) + } + + return nil +} diff --git a/internal/collector/pagefile/pagefile_test.go b/internal/collector/pagefile/pagefile_test.go new file mode 100644 index 00000000..ecd7ff1a --- /dev/null +++ b/internal/collector/pagefile/pagefile_test.go @@ -0,0 +1,16 @@ +package pagefile_test + +import ( + "testing" + + "github.com/prometheus-community/windows_exporter/internal/collector/pagefile" + "github.com/prometheus-community/windows_exporter/internal/testutils" +) + +func BenchmarkCollector(b *testing.B) { + testutils.FuncBenchmarkCollector(b, pagefile.Name, pagefile.NewWithFlags) +} + +func TestCollector(t *testing.T) { + testutils.TestCollector(t, pagefile.New, nil) +} diff --git a/internal/perfdata/v2/collector.go b/internal/perfdata/v2/collector.go index 6e78254f..610ccda1 100644 --- a/internal/perfdata/v2/collector.go +++ b/internal/perfdata/v2/collector.go @@ -5,6 +5,7 @@ package v2 import ( "errors" "fmt" + "slices" "strings" "unsafe" @@ -14,9 +15,10 @@ import ( ) type Collector struct { - object string - counters map[string]Counter - handle pdhQueryHandle + object string + counters map[string]Counter + handle pdhQueryHandle + totalCounterRequested bool } type Counter struct { @@ -39,9 +41,10 @@ func NewCollector(object string, instances []string, counters []string) (*Collec } collector := &Collector{ - object: object, - counters: make(map[string]Counter, len(counters)), - handle: handle, + object: object, + counters: make(map[string]Counter, len(counters)), + handle: handle, + totalCounterRequested: slices.Contains(instances, "_Total"), } for _, counterName := range counters { @@ -166,12 +169,10 @@ func (c *Collector) Collect() (map[string]map[string]perftypes.CounterValues, er metricType = prometheus.GaugeValue } - _, isTotalCounterRequests := c.counters["_Total"] - for _, item := range items { if item.RawValue.CStatus == PdhCstatusValidData || item.RawValue.CStatus == PdhCstatusNewData { instanceName := windows.UTF16PtrToString(item.SzName) - if strings.HasSuffix(instanceName, "_Total") && !isTotalCounterRequests { + if strings.HasSuffix(instanceName, "_Total") && !c.totalCounterRequested { continue } diff --git a/pkg/collector/collector.go b/pkg/collector/collector.go index 014b2937..1892af2f 100644 --- a/pkg/collector/collector.go +++ b/pkg/collector/collector.go @@ -39,6 +39,7 @@ import ( "github.com/prometheus-community/windows_exporter/internal/collector/netframework" "github.com/prometheus-community/windows_exporter/internal/collector/nps" "github.com/prometheus-community/windows_exporter/internal/collector/os" + "github.com/prometheus-community/windows_exporter/internal/collector/pagefile" "github.com/prometheus-community/windows_exporter/internal/collector/perfdata" "github.com/prometheus-community/windows_exporter/internal/collector/physical_disk" "github.com/prometheus-community/windows_exporter/internal/collector/printer" @@ -107,6 +108,7 @@ func NewWithConfig(config Config) *MetricCollectors { collectors[netframework.Name] = netframework.New(&config.NetFramework) collectors[nps.Name] = nps.New(&config.Nps) collectors[os.Name] = os.New(&config.OS) + collectors[pagefile.Name] = pagefile.New(&config.Paging) collectors[perfdata.Name] = perfdata.New(&config.PerfData) collectors[physical_disk.Name] = physical_disk.New(&config.PhysicalDisk) collectors[printer.Name] = printer.New(&config.Printer) diff --git a/pkg/collector/config.go b/pkg/collector/config.go index 879345b1..92205759 100644 --- a/pkg/collector/config.go +++ b/pkg/collector/config.go @@ -29,6 +29,7 @@ import ( "github.com/prometheus-community/windows_exporter/internal/collector/netframework" "github.com/prometheus-community/windows_exporter/internal/collector/nps" "github.com/prometheus-community/windows_exporter/internal/collector/os" + "github.com/prometheus-community/windows_exporter/internal/collector/pagefile" "github.com/prometheus-community/windows_exporter/internal/collector/perfdata" "github.com/prometheus-community/windows_exporter/internal/collector/physical_disk" "github.com/prometheus-community/windows_exporter/internal/collector/printer" @@ -79,6 +80,7 @@ type Config struct { NetFramework netframework.Config `yaml:"net_framework"` Nps nps.Config `yaml:"nps"` OS os.Config `yaml:"os"` + Paging pagefile.Config `yaml:"paging"` PerfData perfdata.Config `yaml:"perf_data"` PhysicalDisk physical_disk.Config `yaml:"physical_disk"` Printer printer.Config `yaml:"printer"` @@ -132,6 +134,7 @@ var ConfigDefaults = Config{ NetFramework: netframework.ConfigDefaults, Nps: nps.ConfigDefaults, OS: os.ConfigDefaults, + Paging: pagefile.ConfigDefaults, PerfData: perfdata.ConfigDefaults, PhysicalDisk: physical_disk.ConfigDefaults, Printer: printer.ConfigDefaults, diff --git a/pkg/collector/map.go b/pkg/collector/map.go index 471f2449..39669afc 100644 --- a/pkg/collector/map.go +++ b/pkg/collector/map.go @@ -33,6 +33,7 @@ import ( "github.com/prometheus-community/windows_exporter/internal/collector/netframework" "github.com/prometheus-community/windows_exporter/internal/collector/nps" "github.com/prometheus-community/windows_exporter/internal/collector/os" + "github.com/prometheus-community/windows_exporter/internal/collector/pagefile" "github.com/prometheus-community/windows_exporter/internal/collector/perfdata" "github.com/prometheus-community/windows_exporter/internal/collector/physical_disk" "github.com/prometheus-community/windows_exporter/internal/collector/printer" @@ -89,6 +90,7 @@ var BuildersWithFlags = map[string]BuilderWithFlags[Collector]{ netframework.Name: NewBuilderWithFlags(netframework.NewWithFlags), nps.Name: NewBuilderWithFlags(nps.NewWithFlags), os.Name: NewBuilderWithFlags(os.NewWithFlags), + pagefile.Name: NewBuilderWithFlags(pagefile.NewWithFlags), perfdata.Name: NewBuilderWithFlags(perfdata.NewWithFlags), physical_disk.Name: NewBuilderWithFlags(physical_disk.NewWithFlags), printer.Name: NewBuilderWithFlags(printer.NewWithFlags), diff --git a/tools/e2e-output.txt b/tools/e2e-output.txt index bd87324d..026253c3 100644 --- a/tools/e2e-output.txt +++ b/tools/e2e-output.txt @@ -122,6 +122,7 @@ windows_exporter_collector_success{collector="logon"} 1 windows_exporter_collector_success{collector="memory"} 1 windows_exporter_collector_success{collector="net"} 1 windows_exporter_collector_success{collector="os"} 1 +windows_exporter_collector_success{collector="pagefile"} 1 windows_exporter_collector_success{collector="perfdata"} 1 windows_exporter_collector_success{collector="physical_disk"} 1 windows_exporter_collector_success{collector="printer"} 1 @@ -144,6 +145,7 @@ windows_exporter_collector_timeout{collector="logon"} 0 windows_exporter_collector_timeout{collector="memory"} 0 windows_exporter_collector_timeout{collector="net"} 0 windows_exporter_collector_timeout{collector="os"} 0 +windows_exporter_collector_timeout{collector="pagefile"} 0 windows_exporter_collector_timeout{collector="perfdata"} 0 windows_exporter_collector_timeout{collector="physical_disk"} 0 windows_exporter_collector_timeout{collector="printer"} 0 @@ -297,9 +299,9 @@ windows_exporter_collector_timeout{collector="udp"} 0 # TYPE windows_os_hostname gauge # HELP windows_os_info Contains full product name & version in labels. Note that the "major_version" for Windows 11 is \\"10\\"; a build number greater than 22000 represents Windows 11. # TYPE windows_os_info gauge -# HELP windows_os_paging_free_bytes OperatingSystem.FreeSpaceInPagingFiles +# HELP windows_os_paging_free_bytes Deprecated: Use windows_pagefile_free_bytes instead. # TYPE windows_os_paging_free_bytes gauge -# HELP windows_os_paging_limit_bytes OperatingSystem.SizeStoredInPagingFiles +# HELP windows_os_paging_limit_bytes Deprecated: Use windows_pagefile_limit_bytes instead. # TYPE windows_os_paging_limit_bytes gauge # HELP windows_os_physical_memory_free_bytes Deprecated: Use `windows_memory_physical_free_bytes` instead. # TYPE windows_os_physical_memory_free_bytes gauge @@ -321,6 +323,10 @@ windows_exporter_collector_timeout{collector="udp"} 0 # TYPE windows_os_virtual_memory_free_bytes gauge # HELP windows_os_visible_memory_bytes Deprecated: Use `windows_memory_physical_total_bytes` instead. # TYPE windows_os_visible_memory_bytes gauge +# HELP windows_pagefile_free_bytes Number of bytes that can be mapped into the operating system paging files without causing any other pages to be swapped out +# TYPE windows_pagefile_free_bytes gauge +# HELP windows_pagefile_limit_bytes Number of bytes that can be stored in the operating system paging files. 0 (zero) indicates that there are no paging files +# TYPE windows_pagefile_limit_bytes gauge # HELP windows_perfdata_memory_cache_faults_sec Performance data for \\Memory\\Cache Faults/sec # TYPE windows_perfdata_memory_cache_faults_sec counter # HELP windows_perfdata_processor_information__privileged_time Performance data for \\Processor Information\\% Privileged Time diff --git a/tools/end-to-end-test.ps1 b/tools/end-to-end-test.ps1 index 5bc81fbe..2a5976ef 100644 --- a/tools/end-to-end-test.ps1 +++ b/tools/end-to-end-test.ps1 @@ -18,14 +18,14 @@ mkdir $textfile_dir | Out-Null Copy-Item 'e2e-textfile.prom' -Destination "$($textfile_dir)/e2e-textfile.prom" # Omit dynamic collector information that will change after each run -$skip_re = "^(go_|windows_exporter_build_info|windows_exporter_collector_duration_seconds|windows_exporter_perflib_snapshot_duration_seconds|windows_exporter_scrape_duration_seconds|process_|windows_textfile_mtime_seconds|windows_cpu|windows_cs|windows_cache|windows_logon|windows_logical_disk|windows_physical_disk|windows_memory|windows_net|windows_os|windows_process|windows_service_process|windows_printer|windows_udp|windows_tcp|windows_system|windows_time|windows_session|windows_perfdata|windows_textfile_mtime_seconds)" +$skip_re = "^(go_|windows_exporter_build_info|windows_exporter_collector_duration_seconds|windows_exporter_perflib_snapshot_duration_seconds|windows_exporter_scrape_duration_seconds|process_|windows_textfile_mtime_seconds|windows_cpu|windows_cs|windows_cache|windows_logon|windows_pagefile|windows_logical_disk|windows_physical_disk|windows_memory|windows_net|windows_os|windows_process|windows_service_process|windows_printer|windows_udp|windows_tcp|windows_system|windows_time|windows_session|windows_perfdata|windows_textfile_mtime_seconds)" # Start process in background, awaiting HTTP requests. # Use default collectors, port and address: http://localhost:9182/metrics $exporter_proc = Start-Process ` -PassThru ` -FilePath ..\windows_exporter.exe ` - -ArgumentList "--log.level=debug","--web.disable-exporter-metrics","--collectors.enabled=[defaults],cpu_info,textfile,process,perfdata,scheduled_task,tcp,udp,time,system,service,logical_disk,printer,os,net,memory,logon,cache","--collector.process.include=explorer.exe","--collector.scheduled_task.include=.*GAEvents","--collector.service.include=Themes","--collector.textfile.directories=$($textfile_dir)",@" + -ArgumentList "--log.level=debug","--web.disable-exporter-metrics","--collectors.enabled=[defaults],cpu_info,textfile,process,pagefile,perfdata,scheduled_task,tcp,udp,time,system,service,logical_disk,printer,os,net,memory,logon,cache","--collector.process.include=explorer.exe","--collector.scheduled_task.include=.*GAEvents","--collector.service.include=Themes","--collector.textfile.directories=$($textfile_dir)",@" --collector.perfdata.objects="[{\"object\":\"Processor Information\",\"instance_label\":\"core\",\"instances\":[\"*\"],\"counters\":{\"% Processor Time\":{},\"% Privileged Time\":{}}},{\"object\":\"Memory\",\"counters\":{\"Cache Faults/sec\":{\"type\":\"counter\"}}}]" "@ ` -WindowStyle Hidden `