collector: derive the collector maps from one registration table (#2730)

Signed-off-by: Jan-Otto Kröpke <mail@jkroepke.de>
This commit is contained in:
Jan-Otto Kröpke
2026-10-15 21:19:47 +00:00
committed by GitHub
parent b914dbbf6e
commit 379b1553bd
10 changed files with 805 additions and 231 deletions
+5 -1
View File
@@ -143,7 +143,11 @@ exercised a live Windows role.
- Keep CLI and YAML validation consistent. Reject unknown fields, unsupported
sub-collector names and invalid values; preserve CLI precedence and repeated
flag/list behavior. Changes to config shapes also need schema, flag parsing
and documentation updates.
and documentation updates. A collector is registered once, in the
`registrations` table of `pkg/collector/map.go`, with a `Config` field whose
YAML key is the collector name and a `docs/collector.<name>.md`;
`TestRegistrations` checks that they agree. The configuration file schema in
`internal/config` only checks keys: the flags parse and validate the values.
- For textfiles, keep HELP/type metadata consistent across files and preserve
unrelated valid series when reporting duplicates or parse failures. Publish
producer output by replacing a temporary file in the same directory. File
+5
View File
@@ -115,6 +115,10 @@ windows_exporter 0.32 rejects configuration it used to ignore. The exporter stop
```
The key `scrape_interval` was accepted by 0.31 but had no effect. It now fails validation.
- **pagefile and smbclient blocks.** In a configuration file, the `collector` blocks of the `pagefile` and `smbclient` collectors are named after the collectors. The keys `paging` and `smb_client` were accepted by 0.31 but had no effect, as both collectors have no options. They now fail validation.
- **Invalid values.** An invalid value in the configuration file, such as `compress: maybe`, stops the exporter with the line and key of the value, also when the same flag is set on the command line.
The configuration file now accepts every value the matching command-line flag accepts: quoted booleans and numbers (`compress: "true"`, `max-size: "10"`), comma-separated lists for list options (`enabled: cluster,node`), YAML lists for comma-separated options (`collectors.enabled: [cpu, memory]`) and YAML merge keys (`<<`). 0.31 rejected them.
Removed settings stop the exporter at startup if they are still set: `--collector.process.counter-version` (see [process collector](#process-collector)), `--collector.container.containerd-state-dir` (see [container collector](#container-collector)) and the collector names `filetime` and `thermalzone` in `--collectors.enabled`. The same applies to their configuration file keys.
@@ -423,6 +427,7 @@ These changes affect Go programs that embed `github.com/prometheus-community/win
- `container.Config.ContainerDStateDir` was replaced by `container.Config.CRIEndpoint`. See [container collector](#container-collector).
- `process.Config.CounterVersion` was removed. See [process collector](#process-collector).
- New field in `collector.Config`: `StorageSpaces`.
- The YAML keys of `collector.Config.Paging` and `collector.Config.SMBClient` are now `pagefile` and `smbclient`, the collector names, instead of `paging` and `smb_client`. The Go field names are unchanged. Both configurations are empty, so this only matters if you decode YAML with unknown fields rejected.
- `scheduled_task.ConfigDefaults.TaskExclude`, also used when `scheduled_task.Config.TaskExclude` is nil, now excludes `/Microsoft/.+`, like the flag. New field `scheduled_task.Config.MissedRuns`, false by default.
- `collector.Config.ThermalZone` was removed together with the `thermalzone` collector.
- New method `Collection.NewHandlerWithContext`. `Collection.NewHandler` is unchanged.
+40 -23
View File
@@ -21,7 +21,10 @@ import (
"errors"
"fmt"
"io"
"maps"
"os"
"reflect"
"slices"
"strings"
"github.com/alecthomas/kingpin/v2"
@@ -30,7 +33,9 @@ import (
)
// configFile represents the structure of the windows_exporter configuration file,
// including configuration from the collector and web packages.
// including configuration from the collector and web packages. Each leaf is a
// flag; the field types document the value of the flag, which parses and
// validates it. [validateKeys] checks the keys of the file against this schema.
type configFile struct {
Debug struct {
Enabled bool `yaml:"enabled"`
@@ -45,17 +50,17 @@ type configFile struct {
Format string `yaml:"format"`
File string `yaml:"file"`
Rotation struct {
MaxSize string `yaml:"max-size"`
MaxBackups string `yaml:"max-backups"`
Compress bool `yaml:"compress"`
MaxSize int `yaml:"max-size"`
MaxBackups int `yaml:"max-backups"`
Compress bool `yaml:"compress"`
} `yaml:"rotation"`
} `yaml:"log"`
Process struct {
Priority string `yaml:"priority"`
MemoryLimit string `yaml:"memory-limit"`
MemoryLimit int64 `yaml:"memory-limit"`
} `yaml:"process"`
Scrape struct {
TimeoutMargin string `yaml:"timeout-margin"`
TimeoutMargin float64 `yaml:"timeout-margin"`
} `yaml:"scrape"`
Telemetry struct {
Path string `yaml:"path"`
@@ -76,6 +81,8 @@ type getFlagger interface {
// Resolver represents a configuration file resolver for kingpin.
type Resolver struct {
flags map[string][]string
// lines maps the keys of the configuration file to their lines.
lines map[string]int
}
// Parse parses the command line arguments and configuration files.
@@ -135,12 +142,9 @@ func NewConfigFileResolver(filePath string) (*Resolver, error) {
_ = file.Close()
}()
var configFileStructure configFile
var document yaml.Node
decoder := yaml.NewDecoder(file)
decoder.KnownFields(true)
if err = decoder.Decode(&configFileStructure); err != nil {
if err = yaml.NewDecoder(file).Decode(&document); err != nil {
// Handle EOF error gracefully, indicating no configuration was found.
if errors.Is(err, io.EOF) {
return &Resolver{flags: flags}, nil
@@ -149,18 +153,18 @@ func NewConfigFileResolver(filePath string) (*Resolver, error) {
return nil, fmt.Errorf("configuration file validation error: %w", err)
}
_, err = file.Seek(0, io.SeekStart)
if err != nil {
return nil, fmt.Errorf("failed to rewind file: %w", err)
}
// Decoding first rejects duplicate keys and expands aliases within the
// limits of the decoder before the schema walks the document.
var rawValues map[string]any
decoder = yaml.NewDecoder(file)
if err = decoder.Decode(&rawValues); err != nil {
if err = document.Decode(&rawValues); err != nil {
return nil, fmt.Errorf("failed to parse configuration file: %w", err)
}
if err = validateKeys(&document, "", reflect.TypeFor[configFile]()); err != nil {
return nil, fmt.Errorf("configuration file validation error: %w", err)
}
// Flatten nested YAML values
flattenedValues := flatten(rawValues)
for k, v := range flattenedValues {
@@ -169,16 +173,25 @@ func NewConfigFileResolver(filePath string) (*Resolver, error) {
}
}
return &Resolver{flags: flags}, nil
lines := map[string]int{}
keyLines(&document, "", lines)
return &Resolver{flags: flags, lines: lines}, nil
}
func (c *Resolver) setDefault(v getFlagger) {
for name, values := range c.flags {
func (c *Resolver) setDefault(v getFlagger) error {
for _, name := range slices.Sorted(maps.Keys(c.flags)) {
values := c.flags[name]
f := v.GetFlag(name)
if f == nil {
continue
}
if err := checkFlagValue(f, values); err != nil {
return fmt.Errorf("line %d: %s: %w", c.lines[name], name, err)
}
// A list sets a repeatable flag, like web.listen-address, once per element.
// Other flags take the list as a comma-separated value.
if repeatable, ok := f.Model().Value.(interface{ IsCumulative() bool }); ok && repeatable.IsCumulative() {
@@ -187,6 +200,8 @@ func (c *Resolver) setDefault(v getFlagger) {
f.Default(strings.Join(values, ","))
}
}
return nil
}
// Bind sets active flags with their default values from the configuration file(s).
@@ -197,10 +212,12 @@ func (c *Resolver) Bind(app *kingpin.Application, args []string) error {
return err
}
c.setDefault(app)
if err = c.setDefault(app); err != nil {
return err
}
if pc.SelectedCommand != nil {
c.setDefault(pc.SelectedCommand)
return c.setDefault(pc.SelectedCommand)
}
return nil
+94
View File
@@ -109,6 +109,21 @@ func TestNewConfigFileResolver(t *testing.T) {
contents: "collector:\n registry:\n keys: |-\n - name: test\n key: HKLM\\SOFTWARE\n values:\n - name: test\n metrc: test\n",
errorText: "field metrc not found in type registry.Value",
},
{name: "quoted bool", contents: "log:\n rotation:\n compress: \"true\"\n"},
{name: "quoted number", contents: "log:\n rotation:\n max-size: \"10\"\n"},
{name: "list as string", contents: "collector:\n mscluster:\n enabled: cluster,node\n"},
{name: "string as list", contents: "collectors:\n enabled: [cpu, memory]\n"},
{name: "duration as string", contents: "collector:\n hyperv:\n virtual-hard-disk:\n refresh-interval: 30s\n"},
{name: "renamed collector block", contents: "collector:\n pagefile:\n smbclient:\n"},
{name: "old collector block", contents: "collector:\n paging:\n", errorText: "line 2: field paging not found in collector"},
{name: "unknown nested key", contents: "collector:\n cpu:\n unknown: true\n", errorText: "line 3: field unknown not found in collector.cpu"},
{name: "unknown top-level key", contents: "unknown: value\n", errorText: "line 1: field unknown not found in the configuration"},
{name: "mapping as value", contents: "log:\n level:\n debug: true\n", errorText: "line 3: log.level must be a value or a list of values"},
{name: "mapping in list", contents: "web:\n listen-address:\n - port: 9182\n", errorText: "line 3: web.listen-address must be a value or a list of values"},
{name: "value as section", contents: "log: debug\n", errorText: "line 1: log must be a mapping"},
{name: "merge key", contents: "collector:\n service: &filter\n include: a\n process:\n <<: *filter\n exclude: b\n"},
{name: "merged section", contents: "log: &log\n level: debug\nweb:\n <<: *log\n", errorText: "line 2: field level not found in web"},
{name: "duplicate key", contents: "log:\n level: debug\n level: info\n", errorText: "already defined"},
{
name: "unknown wmi option",
contents: "collector:\n wmi:\n query: |-\n - name: test\n",
@@ -174,6 +189,11 @@ func TestNewConfigFileResolverFlags(t *testing.T) {
value, values := sampleConfigValue(t, field, block)
// These flags only accept some strings.
if sample, ok := map[string]string{"log.level": "debug", "log.format": "json"}[flag.Name]; ok {
value, values = sample, []string{sample}
}
contents := value
for _, key := range slices.Backward(keys) {
contents = map[string]any{key: contents}
@@ -431,6 +451,8 @@ func sampleConfigValue(t *testing.T, typ reflect.Type, block bool) (any, []strin
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return 1, []string{"1"}
case reflect.Float32, reflect.Float64:
return 1.5, []string{"1.5"}
case reflect.Slice, reflect.Interface:
return []string{"test1", "test2"}, []string{"test1", "test2"}
case reflect.String:
@@ -525,3 +547,75 @@ func TestParseEmptyValue(t *testing.T) {
})
}
}
// TestParseFlagValues verifies that the configuration file takes the values the
// flags accept, whatever the Go type of the configuration, and that an invalid
// value fails with its key and line, even if the command line sets the flag.
func TestParseFlagValues(t *testing.T) {
t.Parallel()
mainFlags := mainFlagNames(t)
for _, tc := range []struct {
name string
config string
args []string
flag string
want string
errorText string
}{
{name: "quoted bool", config: "log:\n rotation:\n compress: \"true\"\n", flag: "log.rotation.compress", want: "true"},
{name: "quoted int", config: "log:\n rotation:\n max-size: \"10\"\n", flag: "log.rotation.max-size", want: "10"},
{
name: "comma-separated list", config: "collector:\n mscluster:\n enabled: cluster,node\n",
flag: "collector.mscluster.enabled", want: "cluster,node",
},
{
name: "list of strings", config: "collector:\n mscluster:\n enabled: [cluster, node]\n",
flag: "collector.mscluster.enabled", want: "cluster,node",
},
{
name: "repeatable flag", config: "web:\n listen-address: ['127.0.0.1:9182', '[::1]:9182']\n",
flag: "web.listen-address", want: "127.0.0.1:9182,[::1]:9182",
},
{
name: "merge key", config: "collector:\n service: &filter\n include: a\n process:\n <<: *filter\n exclude: b\n",
flag: "collector.process.include", want: "a",
},
{
name: "CLI overrides config", config: "log:\n rotation:\n compress: true\n",
args: []string{"--no-log.rotation.compress"}, flag: "log.rotation.compress", want: "false",
},
{
name: "invalid bool", config: "log:\n rotation:\n compress: maybe\n",
errorText: "line 3: log.rotation.compress: strconv.ParseBool",
},
{
name: "invalid int", config: "debug:\n enabled: true\nlog:\n rotation:\n max-size: ten\n",
errorText: "line 5: log.rotation.max-size: strconv.Parse",
},
{
name: "invalid value set on the CLI", config: "log:\n rotation:\n compress: maybe\n",
args: []string{"--log.rotation.compress"}, errorText: "line 3: log.rotation.compress: strconv.ParseBool",
},
} {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
path := filepath.Join(t.TempDir(), "config.yaml")
require.NoError(t, os.WriteFile(path, []byte(tc.config), 0o600))
app := newApp(mainFlags)
err := config.Parse(app, append([]string{"--config.file=" + path}, tc.args...))
if tc.errorText != "" {
require.ErrorContains(t, err, tc.errorText)
return
}
require.NoError(t, err)
require.Equal(t, tc.want, app.GetFlag(tc.flag).Model().Value.String())
})
}
}
+264
View File
@@ -0,0 +1,264 @@
// 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 config
import (
"fmt"
"reflect"
"slices"
"strings"
"github.com/alecthomas/kingpin/v2"
"go.yaml.in/yaml/v3"
)
// validateKeys checks that node, the value of the dotted key path, only uses
// the keys of the schema typ.
//
// The configuration file sets flags, and the flags parse and validate the
// values (see [checkFlagValue]). So the schema only defines the keys: a leaf
// takes any scalar or a list of scalars, whatever the type of its field.
// Decoding the values into the field types would reject values that the flags
// accept, like compress: "true" or a comma-separated list for a list field.
//
// A struct that decodes itself, like the performancecounter block, is decoded
// to validate its contents.
func validateKeys(node *yaml.Node, path string, typ reflect.Type) error {
switch node.Kind {
case yaml.DocumentNode:
for _, content := range node.Content {
if err := validateKeys(content, path, typ); err != nil {
return err
}
}
return nil
case yaml.AliasNode:
return validateKeys(node.Alias, path, typ)
case yaml.ScalarNode:
if node.Tag == "!!null" {
// A key without a value leaves the flags unchanged.
return nil
}
case yaml.MappingNode, yaml.SequenceNode:
}
if reflect.PointerTo(typ).Implements(reflect.TypeFor[yaml.Unmarshaler]()) {
//nolint:wrapcheck // the decoder errors name the line and field.
return node.Decode(reflect.New(typ).Interface())
}
if typ.Kind() != reflect.Struct {
if !isValue(node) {
return fmt.Errorf("line %d: %s must be a value or a list of values", node.Line, displayPath(path))
}
return nil
}
if node.Kind != yaml.MappingNode {
return fmt.Errorf("line %d: %s must be a mapping", node.Line, displayPath(path))
}
for i := 0; i+1 < len(node.Content); i += 2 {
key, value := node.Content[i], node.Content[i+1]
if isMerge(key) {
// A merge key (<<) adds the keys of other mappings.
for _, source := range mergeSources(value) {
if err := validateKeys(source, path, typ); err != nil {
return err
}
}
continue
}
field, ok := schemaField(typ, key.Value)
if !ok {
return fmt.Errorf("line %d: field %s not found in %s", key.Line, key.Value, displayPath(path))
}
if err := validateKeys(value, joinPath(path, key.Value), field.Type); err != nil {
return err
}
}
return nil
}
// isValue reports whether node is the value of a flag: a scalar, or a list of
// scalars for a repeatable or list flag.
func isValue(node *yaml.Node) bool {
switch node.Kind {
case yaml.ScalarNode:
return true
case yaml.AliasNode:
return isValue(node.Alias)
case yaml.SequenceNode:
for _, item := range node.Content {
if item.Kind == yaml.AliasNode {
item = item.Alias
}
if item.Kind != yaml.ScalarNode {
return false
}
}
return true
case yaml.DocumentNode, yaml.MappingNode:
}
return false
}
// keyLines records the line of every dotted key path below node in lines.
// Explicit keys take precedence over the keys of merged mappings, like in the
// decoded values.
func keyLines(node *yaml.Node, path string, lines map[string]int) {
switch node.Kind {
case yaml.DocumentNode:
for _, content := range node.Content {
keyLines(content, path, lines)
}
case yaml.AliasNode:
keyLines(node.Alias, path, lines)
case yaml.MappingNode:
for i := 0; i+1 < len(node.Content); i += 2 {
if isMerge(node.Content[i]) {
for _, source := range mergeSources(node.Content[i+1]) {
keyLines(source, path, lines)
}
}
}
for i := 0; i+1 < len(node.Content); i += 2 {
key, value := node.Content[i], node.Content[i+1]
if isMerge(key) {
continue
}
keyPath := joinPath(path, key.Value)
lines[keyPath] = key.Line
keyLines(value, keyPath, lines)
}
case yaml.ScalarNode, yaml.SequenceNode:
}
}
func isMerge(key *yaml.Node) bool {
return key.Kind == yaml.ScalarNode && key.Tag == "!!merge"
}
// mergeSources returns the mappings that a merge key merges: an alias, a
// mapping or a list of them.
func mergeSources(value *yaml.Node) []*yaml.Node {
if value.Kind == yaml.SequenceNode {
return value.Content
}
return []*yaml.Node{value}
}
func joinPath(path, key string) string {
if path == "" {
return key
}
return path + "." + key
}
func displayPath(path string) string {
if path == "" {
return "the configuration"
}
return path
}
// schemaField returns the field of the struct typ with the YAML key name,
// looking into the fields inlined with the yaml inline option.
func schemaField(typ reflect.Type, name string) (reflect.StructField, bool) {
for field := range typ.Fields() {
if !field.IsExported() {
continue
}
key, options, _ := strings.Cut(field.Tag.Get("yaml"), ",")
if slices.Contains(strings.Split(options, ","), "inline") && field.Type.Kind() == reflect.Struct {
if inlined, ok := schemaField(field.Type, name); ok {
return inlined, true
}
continue
}
if key == "" {
key = strings.ToLower(field.Name)
}
if key != "-" && key == name {
return field, true
}
}
return reflect.StructField{}, false
}
// checkFlagValue checks that the flag accepts the values from the
// configuration file.
//
// The flags only parse their default values in kingpin.Application.Parse,
// which does not tell the configuration key, and does not parse them at all
// if the flag is also set on the command line. kingpin cannot copy a flag
// value, so the check sets the flag value itself. That is safe: Parse sets it
// again, to the same default or to the command-line value. A repeatable flag
// would append the values twice, so its list is restored after the check.
func checkFlagValue(flag *kingpin.FlagClause, values []string) error {
value := flag.Model().Value
if repeatable, ok := value.(interface{ IsCumulative() bool }); !ok || !repeatable.IsCumulative() {
//nolint:wrapcheck // the caller adds the key and line.
return value.Set(strings.Join(values, ","))
}
getter, ok := value.(kingpin.Getter)
if !ok {
return nil
}
list := reflect.ValueOf(getter.Get())
if list.Kind() != reflect.Pointer || list.Elem().Kind() != reflect.Slice {
// The list cannot be restored; Parse checks the values.
return nil
}
saved := reflect.ValueOf(list.Elem().Interface())
defer list.Elem().Set(saved)
for _, v := range values {
if err := value.Set(v); err != nil {
//nolint:wrapcheck // the caller adds the key and line.
return err
}
}
return nil
}
+69
View File
@@ -0,0 +1,69 @@
// 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 config
import (
"reflect"
"testing"
"github.com/stretchr/testify/require"
"go.yaml.in/yaml/v3"
)
// TestValidateKeysInline verifies that the keys of a field inlined with the
// yaml inline option belong to the enclosing mapping.
func TestValidateKeysInline(t *testing.T) {
t.Parallel()
type Inner struct {
Level string `yaml:"level"`
}
type schema struct {
Log struct {
Inner `yaml:",inline"`
Format string `yaml:"format"`
} `yaml:"log"`
}
for _, tc := range []struct {
name string
contents string
errorText string
}{
{name: "inlined key", contents: "log:\n level: debug\n format: json\n"},
{name: "unknown key", contents: "log:\n inner: debug\n", errorText: "line 2: field inner not found in log"},
} {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
var document yaml.Node
require.NoError(t, yaml.Unmarshal([]byte(tc.contents), &document))
err := validateKeys(&document, "", reflect.TypeFor[schema]())
if tc.errorText != "" {
require.EqualError(t, err, tc.errorText)
return
}
require.NoError(t, err)
})
}
}
+4 -101
View File
@@ -28,56 +28,6 @@ import (
gotime "time"
"github.com/alecthomas/kingpin/v2"
"github.com/prometheus-community/windows_exporter/internal/collector/ad"
"github.com/prometheus-community/windows_exporter/internal/collector/adcs"
"github.com/prometheus-community/windows_exporter/internal/collector/adfs"
"github.com/prometheus-community/windows_exporter/internal/collector/cache"
"github.com/prometheus-community/windows_exporter/internal/collector/container"
"github.com/prometheus-community/windows_exporter/internal/collector/cpu"
"github.com/prometheus-community/windows_exporter/internal/collector/cpu_info"
"github.com/prometheus-community/windows_exporter/internal/collector/dfsr"
"github.com/prometheus-community/windows_exporter/internal/collector/dhcp"
"github.com/prometheus-community/windows_exporter/internal/collector/diskdrive"
"github.com/prometheus-community/windows_exporter/internal/collector/dmi"
"github.com/prometheus-community/windows_exporter/internal/collector/dns"
"github.com/prometheus-community/windows_exporter/internal/collector/exchange"
"github.com/prometheus-community/windows_exporter/internal/collector/file"
"github.com/prometheus-community/windows_exporter/internal/collector/fsrmquota"
"github.com/prometheus-community/windows_exporter/internal/collector/gpu"
"github.com/prometheus-community/windows_exporter/internal/collector/hyperv"
"github.com/prometheus-community/windows_exporter/internal/collector/iis"
"github.com/prometheus-community/windows_exporter/internal/collector/license"
"github.com/prometheus-community/windows_exporter/internal/collector/logical_disk"
"github.com/prometheus-community/windows_exporter/internal/collector/memory"
"github.com/prometheus-community/windows_exporter/internal/collector/mscluster"
"github.com/prometheus-community/windows_exporter/internal/collector/msmq"
"github.com/prometheus-community/windows_exporter/internal/collector/mssql"
"github.com/prometheus-community/windows_exporter/internal/collector/net"
"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/performancecounter"
"github.com/prometheus-community/windows_exporter/internal/collector/physical_disk"
"github.com/prometheus-community/windows_exporter/internal/collector/printer"
"github.com/prometheus-community/windows_exporter/internal/collector/process"
"github.com/prometheus-community/windows_exporter/internal/collector/registry"
"github.com/prometheus-community/windows_exporter/internal/collector/remote_fx"
"github.com/prometheus-community/windows_exporter/internal/collector/scheduled_task"
"github.com/prometheus-community/windows_exporter/internal/collector/service"
"github.com/prometheus-community/windows_exporter/internal/collector/smb"
"github.com/prometheus-community/windows_exporter/internal/collector/smbclient"
"github.com/prometheus-community/windows_exporter/internal/collector/smtp"
"github.com/prometheus-community/windows_exporter/internal/collector/storage_spaces"
"github.com/prometheus-community/windows_exporter/internal/collector/system"
"github.com/prometheus-community/windows_exporter/internal/collector/tcp"
"github.com/prometheus-community/windows_exporter/internal/collector/terminal_services"
"github.com/prometheus-community/windows_exporter/internal/collector/textfile"
"github.com/prometheus-community/windows_exporter/internal/collector/time"
"github.com/prometheus-community/windows_exporter/internal/collector/udp"
"github.com/prometheus-community/windows_exporter/internal/collector/update"
"github.com/prometheus-community/windows_exporter/internal/collector/vmware"
"github.com/prometheus-community/windows_exporter/internal/collector/wmi"
"github.com/prometheus-community/windows_exporter/internal/mi"
"github.com/prometheus-community/windows_exporter/internal/pdh"
"github.com/prometheus-community/windows_exporter/internal/types"
@@ -100,57 +50,10 @@ func NewWithFlags(app *kingpin.Application) *Collection {
//
//goland:noinspection GoUnusedExportedFunction
func NewWithConfig(config Config) *Collection {
collectors := Map{}
collectors[ad.Name] = ad.New(&config.AD)
collectors[adcs.Name] = adcs.New(&config.ADCS)
collectors[adfs.Name] = adfs.New(&config.ADFS)
collectors[cache.Name] = cache.New(&config.Cache)
collectors[container.Name] = container.New(&config.Container)
collectors[cpu.Name] = cpu.New(&config.CPU)
collectors[cpu_info.Name] = cpu_info.New(&config.CPUInfo)
collectors[dfsr.Name] = dfsr.New(&config.DFSR)
collectors[dhcp.Name] = dhcp.New(&config.Dhcp)
collectors[diskdrive.Name] = diskdrive.New(&config.DiskDrive)
collectors[dmi.Name] = dmi.New(&config.DMI)
collectors[dns.Name] = dns.New(&config.DNS)
collectors[exchange.Name] = exchange.New(&config.Exchange)
collectors[file.Name] = file.New(&config.File)
collectors[fsrmquota.Name] = fsrmquota.New(&config.Fsrmquota)
collectors[gpu.Name] = gpu.New(&config.GPU)
collectors[hyperv.Name] = hyperv.New(&config.HyperV)
collectors[iis.Name] = iis.New(&config.IIS)
collectors[license.Name] = license.New(&config.License)
collectors[logical_disk.Name] = logical_disk.New(&config.LogicalDisk)
collectors[memory.Name] = memory.New(&config.Memory)
collectors[mscluster.Name] = mscluster.New(&config.MSCluster)
collectors[msmq.Name] = msmq.New(&config.Msmq)
collectors[mssql.Name] = mssql.New(&config.Mssql)
collectors[net.Name] = net.New(&config.Net)
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[performancecounter.Name] = performancecounter.New(&config.PerformanceCounter)
collectors[physical_disk.Name] = physical_disk.New(&config.PhysicalDisk)
collectors[printer.Name] = printer.New(&config.Printer)
collectors[process.Name] = process.New(&config.Process)
collectors[registry.Name] = registry.New(&config.Registry)
collectors[remote_fx.Name] = remote_fx.New(&config.RemoteFx)
collectors[scheduled_task.Name] = scheduled_task.New(&config.ScheduledTask)
collectors[service.Name] = service.New(&config.Service)
collectors[smb.Name] = smb.New(&config.SMB)
collectors[smbclient.Name] = smbclient.New(&config.SMBClient)
collectors[smtp.Name] = smtp.New(&config.SMTP)
collectors[storage_spaces.Name] = storage_spaces.New(&config.StorageSpaces)
collectors[system.Name] = system.New(&config.System)
collectors[tcp.Name] = tcp.New(&config.TCP)
collectors[terminal_services.Name] = terminal_services.New(&config.TerminalServices)
collectors[textfile.Name] = textfile.New(&config.Textfile)
collectors[time.Name] = time.New(&config.Time)
collectors[udp.Name] = udp.New(&config.UDP)
collectors[update.Name] = update.New(&config.Update)
collectors[vmware.Name] = vmware.New(&config.Vmware)
collectors[wmi.Name] = wmi.New(&config.WMI)
collectors := make(Map, len(registrations))
for _, r := range registrations {
collectors[r.name] = r.newWithConfig(&config)
}
return New(collectors)
}
+10 -54
View File
@@ -99,7 +99,7 @@ type Config struct {
NetFramework netframework.Config `yaml:"netframework"`
Nps nps.Config `yaml:"nps"`
OS os.Config `yaml:"os"`
Paging pagefile.Config `yaml:"paging"`
Paging pagefile.Config `yaml:"pagefile"`
PerformanceCounter performancecounter.Config `yaml:"performancecounter"`
PhysicalDisk physical_disk.Config `yaml:"physical_disk"`
Printer printer.Config `yaml:"printer"`
@@ -109,7 +109,7 @@ type Config struct {
ScheduledTask scheduled_task.Config `yaml:"scheduled_task"`
Service service.Config `yaml:"service"`
SMB smb.Config `yaml:"smb"`
SMBClient smbclient.Config `yaml:"smb_client"`
SMBClient smbclient.Config `yaml:"smbclient"`
SMTP smtp.Config `yaml:"smtp"`
StorageSpaces storage_spaces.Config `yaml:"storage_spaces"`
System system.Config `yaml:"system"`
@@ -127,55 +127,11 @@ type Config struct {
//
//nolint:gochecknoglobals
//goland:noinspection GoUnusedGlobalVariable
var ConfigDefaults = Config{
AD: ad.ConfigDefaults,
ADCS: adcs.ConfigDefaults,
ADFS: adfs.ConfigDefaults,
Cache: cache.ConfigDefaults,
Container: container.ConfigDefaults,
CPU: cpu.ConfigDefaults,
CPUInfo: cpu_info.ConfigDefaults,
DFSR: dfsr.ConfigDefaults,
Dhcp: dhcp.ConfigDefaults,
DiskDrive: diskdrive.ConfigDefaults,
DMI: dmi.ConfigDefaults,
DNS: dns.ConfigDefaults,
Exchange: exchange.ConfigDefaults,
File: file.ConfigDefaults,
Fsrmquota: fsrmquota.ConfigDefaults,
GPU: gpu.ConfigDefaults,
HyperV: hyperv.ConfigDefaults,
IIS: iis.ConfigDefaults,
License: license.ConfigDefaults,
LogicalDisk: logical_disk.ConfigDefaults,
Memory: memory.ConfigDefaults,
MSCluster: mscluster.ConfigDefaults,
Msmq: msmq.ConfigDefaults,
Mssql: mssql.ConfigDefaults,
Net: net.ConfigDefaults,
NetFramework: netframework.ConfigDefaults,
Nps: nps.ConfigDefaults,
OS: os.ConfigDefaults,
Paging: pagefile.ConfigDefaults,
PerformanceCounter: performancecounter.ConfigDefaults,
PhysicalDisk: physical_disk.ConfigDefaults,
Printer: printer.ConfigDefaults,
Process: process.ConfigDefaults,
Registry: registry.ConfigDefaults,
RemoteFx: remote_fx.ConfigDefaults,
ScheduledTask: scheduled_task.ConfigDefaults,
Service: service.ConfigDefaults,
SMB: smb.ConfigDefaults,
SMBClient: smbclient.ConfigDefaults,
SMTP: smtp.ConfigDefaults,
StorageSpaces: storage_spaces.ConfigDefaults,
System: system.ConfigDefaults,
TCP: tcp.ConfigDefaults,
TerminalServices: terminal_services.ConfigDefaults,
Textfile: textfile.ConfigDefaults,
Time: time.ConfigDefaults,
UDP: udp.ConfigDefaults,
Update: update.ConfigDefaults,
Vmware: vmware.ConfigDefaults,
WMI: wmi.ConfigDefaults,
}
var ConfigDefaults = func() Config {
var config Config
for _, r := range registrations {
r.setDefaults(&config)
}
return config
}()
+101 -52
View File
@@ -74,65 +74,114 @@ import (
"github.com/prometheus-community/windows_exporter/internal/collector/wmi"
)
// registration ties a collector name to its constructors, its field in
// [Config] and its default configuration. It is the single source of
// [BuildersWithFlags], [NewWithConfig] and [ConfigDefaults].
type registration struct {
name string
newWithFlags BuilderWithFlags[Collector]
newWithConfig func(config *Config) Collector
setDefaults func(config *Config)
// field returns the address of the collector's field in config. Tests
// use it to match the registrations to the fields of [Config].
field func(config *Config) any
}
// register returns the registration of a collector. Its type parameters
// ensure that the constructors, the defaults and the [Config] field use the
// same configuration type.
func register[C Collector, T any](
name string,
newWithConfig func(*T) C,
newWithFlags BuilderWithFlags[C],
defaults T,
field func(*Config) *T,
) registration {
return registration{
name: name,
newWithFlags: NewBuilderWithFlags(newWithFlags),
newWithConfig: func(config *Config) Collector { return newWithConfig(field(config)) },
setDefaults: func(config *Config) { *field(config) = defaults },
field: func(config *Config) any { return field(config) },
}
}
// registrations lists every built-in collector. A new collector needs its
// field in [Config], its documentation in docs/collector.<name>.md and one
// entry here.
//
//nolint:gochecknoglobals
var registrations = []registration{
register(ad.Name, ad.New, ad.NewWithFlags, ad.ConfigDefaults, func(c *Config) *ad.Config { return &c.AD }),
register(adcs.Name, adcs.New, adcs.NewWithFlags, adcs.ConfigDefaults, func(c *Config) *adcs.Config { return &c.ADCS }),
register(adfs.Name, adfs.New, adfs.NewWithFlags, adfs.ConfigDefaults, func(c *Config) *adfs.Config { return &c.ADFS }),
register(cache.Name, cache.New, cache.NewWithFlags, cache.ConfigDefaults, func(c *Config) *cache.Config { return &c.Cache }),
register(container.Name, container.New, container.NewWithFlags, container.ConfigDefaults, func(c *Config) *container.Config { return &c.Container }),
register(cpu.Name, cpu.New, cpu.NewWithFlags, cpu.ConfigDefaults, func(c *Config) *cpu.Config { return &c.CPU }),
register(cpu_info.Name, cpu_info.New, cpu_info.NewWithFlags, cpu_info.ConfigDefaults, func(c *Config) *cpu_info.Config { return &c.CPUInfo }),
register(dfsr.Name, dfsr.New, dfsr.NewWithFlags, dfsr.ConfigDefaults, func(c *Config) *dfsr.Config { return &c.DFSR }),
register(dhcp.Name, dhcp.New, dhcp.NewWithFlags, dhcp.ConfigDefaults, func(c *Config) *dhcp.Config { return &c.Dhcp }),
register(diskdrive.Name, diskdrive.New, diskdrive.NewWithFlags, diskdrive.ConfigDefaults, func(c *Config) *diskdrive.Config { return &c.DiskDrive }),
register(dmi.Name, dmi.New, dmi.NewWithFlags, dmi.ConfigDefaults, func(c *Config) *dmi.Config { return &c.DMI }),
register(dns.Name, dns.New, dns.NewWithFlags, dns.ConfigDefaults, func(c *Config) *dns.Config { return &c.DNS }),
register(exchange.Name, exchange.New, exchange.NewWithFlags, exchange.ConfigDefaults, func(c *Config) *exchange.Config { return &c.Exchange }),
register(file.Name, file.New, file.NewWithFlags, file.ConfigDefaults, func(c *Config) *file.Config { return &c.File }),
register(fsrmquota.Name, fsrmquota.New, fsrmquota.NewWithFlags, fsrmquota.ConfigDefaults, func(c *Config) *fsrmquota.Config { return &c.Fsrmquota }),
register(gpu.Name, gpu.New, gpu.NewWithFlags, gpu.ConfigDefaults, func(c *Config) *gpu.Config { return &c.GPU }),
register(hyperv.Name, hyperv.New, hyperv.NewWithFlags, hyperv.ConfigDefaults, func(c *Config) *hyperv.Config { return &c.HyperV }),
register(iis.Name, iis.New, iis.NewWithFlags, iis.ConfigDefaults, func(c *Config) *iis.Config { return &c.IIS }),
register(license.Name, license.New, license.NewWithFlags, license.ConfigDefaults, func(c *Config) *license.Config { return &c.License }),
register(logical_disk.Name, logical_disk.New, logical_disk.NewWithFlags, logical_disk.ConfigDefaults, func(c *Config) *logical_disk.Config { return &c.LogicalDisk }),
register(memory.Name, memory.New, memory.NewWithFlags, memory.ConfigDefaults, func(c *Config) *memory.Config { return &c.Memory }),
register(mscluster.Name, mscluster.New, mscluster.NewWithFlags, mscluster.ConfigDefaults, func(c *Config) *mscluster.Config { return &c.MSCluster }),
register(msmq.Name, msmq.New, msmq.NewWithFlags, msmq.ConfigDefaults, func(c *Config) *msmq.Config { return &c.Msmq }),
register(mssql.Name, mssql.New, mssql.NewWithFlags, mssql.ConfigDefaults, func(c *Config) *mssql.Config { return &c.Mssql }),
register(net.Name, net.New, net.NewWithFlags, net.ConfigDefaults, func(c *Config) *net.Config { return &c.Net }),
register(netframework.Name, netframework.New, netframework.NewWithFlags, netframework.ConfigDefaults, func(c *Config) *netframework.Config { return &c.NetFramework }),
register(nps.Name, nps.New, nps.NewWithFlags, nps.ConfigDefaults, func(c *Config) *nps.Config { return &c.Nps }),
register(os.Name, os.New, os.NewWithFlags, os.ConfigDefaults, func(c *Config) *os.Config { return &c.OS }),
register(pagefile.Name, pagefile.New, pagefile.NewWithFlags, pagefile.ConfigDefaults, func(c *Config) *pagefile.Config { return &c.Paging }),
register(performancecounter.Name, performancecounter.New, performancecounter.NewWithFlags, performancecounter.ConfigDefaults, func(c *Config) *performancecounter.Config { return &c.PerformanceCounter }),
register(physical_disk.Name, physical_disk.New, physical_disk.NewWithFlags, physical_disk.ConfigDefaults, func(c *Config) *physical_disk.Config { return &c.PhysicalDisk }),
register(printer.Name, printer.New, printer.NewWithFlags, printer.ConfigDefaults, func(c *Config) *printer.Config { return &c.Printer }),
register(process.Name, process.New, process.NewWithFlags, process.ConfigDefaults, func(c *Config) *process.Config { return &c.Process }),
register(registry.Name, registry.New, registry.NewWithFlags, registry.ConfigDefaults, func(c *Config) *registry.Config { return &c.Registry }),
register(remote_fx.Name, remote_fx.New, remote_fx.NewWithFlags, remote_fx.ConfigDefaults, func(c *Config) *remote_fx.Config { return &c.RemoteFx }),
register(scheduled_task.Name, scheduled_task.New, scheduled_task.NewWithFlags, scheduled_task.ConfigDefaults, func(c *Config) *scheduled_task.Config { return &c.ScheduledTask }),
register(service.Name, service.New, service.NewWithFlags, service.ConfigDefaults, func(c *Config) *service.Config { return &c.Service }),
register(smb.Name, smb.New, smb.NewWithFlags, smb.ConfigDefaults, func(c *Config) *smb.Config { return &c.SMB }),
register(smbclient.Name, smbclient.New, smbclient.NewWithFlags, smbclient.ConfigDefaults, func(c *Config) *smbclient.Config { return &c.SMBClient }),
register(smtp.Name, smtp.New, smtp.NewWithFlags, smtp.ConfigDefaults, func(c *Config) *smtp.Config { return &c.SMTP }),
register(storage_spaces.Name, storage_spaces.New, storage_spaces.NewWithFlags, storage_spaces.ConfigDefaults, func(c *Config) *storage_spaces.Config { return &c.StorageSpaces }),
register(system.Name, system.New, system.NewWithFlags, system.ConfigDefaults, func(c *Config) *system.Config { return &c.System }),
register(tcp.Name, tcp.New, tcp.NewWithFlags, tcp.ConfigDefaults, func(c *Config) *tcp.Config { return &c.TCP }),
register(terminal_services.Name, terminal_services.New, terminal_services.NewWithFlags, terminal_services.ConfigDefaults, func(c *Config) *terminal_services.Config { return &c.TerminalServices }),
register(textfile.Name, textfile.New, textfile.NewWithFlags, textfile.ConfigDefaults, func(c *Config) *textfile.Config { return &c.Textfile }),
register(time.Name, time.New, time.NewWithFlags, time.ConfigDefaults, func(c *Config) *time.Config { return &c.Time }),
register(udp.Name, udp.New, udp.NewWithFlags, udp.ConfigDefaults, func(c *Config) *udp.Config { return &c.UDP }),
register(update.Name, update.New, update.NewWithFlags, update.ConfigDefaults, func(c *Config) *update.Config { return &c.Update }),
register(vmware.Name, vmware.New, vmware.NewWithFlags, vmware.ConfigDefaults, func(c *Config) *vmware.Config { return &c.Vmware }),
register(wmi.Name, wmi.New, wmi.NewWithFlags, wmi.ConfigDefaults, func(c *Config) *wmi.Config { return &c.WMI }),
}
func NewBuilderWithFlags[C Collector](fn BuilderWithFlags[C]) BuilderWithFlags[Collector] {
return func(app *kingpin.Application) Collector {
return fn(app)
}
}
// BuildersWithFlags holds the flag-based constructor of every built-in
// collector, keyed by the collector name.
//
//nolint:gochecknoglobals
var BuildersWithFlags = map[string]BuilderWithFlags[Collector]{
ad.Name: NewBuilderWithFlags(ad.NewWithFlags),
adcs.Name: NewBuilderWithFlags(adcs.NewWithFlags),
adfs.Name: NewBuilderWithFlags(adfs.NewWithFlags),
cache.Name: NewBuilderWithFlags(cache.NewWithFlags),
container.Name: NewBuilderWithFlags(container.NewWithFlags),
cpu.Name: NewBuilderWithFlags(cpu.NewWithFlags),
cpu_info.Name: NewBuilderWithFlags(cpu_info.NewWithFlags),
dfsr.Name: NewBuilderWithFlags(dfsr.NewWithFlags),
dhcp.Name: NewBuilderWithFlags(dhcp.NewWithFlags),
diskdrive.Name: NewBuilderWithFlags(diskdrive.NewWithFlags),
dmi.Name: NewBuilderWithFlags(dmi.NewWithFlags),
dns.Name: NewBuilderWithFlags(dns.NewWithFlags),
exchange.Name: NewBuilderWithFlags(exchange.NewWithFlags),
file.Name: NewBuilderWithFlags(file.NewWithFlags),
fsrmquota.Name: NewBuilderWithFlags(fsrmquota.NewWithFlags),
gpu.Name: NewBuilderWithFlags(gpu.NewWithFlags),
hyperv.Name: NewBuilderWithFlags(hyperv.NewWithFlags),
iis.Name: NewBuilderWithFlags(iis.NewWithFlags),
license.Name: NewBuilderWithFlags(license.NewWithFlags),
logical_disk.Name: NewBuilderWithFlags(logical_disk.NewWithFlags),
memory.Name: NewBuilderWithFlags(memory.NewWithFlags),
mscluster.Name: NewBuilderWithFlags(mscluster.NewWithFlags),
msmq.Name: NewBuilderWithFlags(msmq.NewWithFlags),
mssql.Name: NewBuilderWithFlags(mssql.NewWithFlags),
net.Name: NewBuilderWithFlags(net.NewWithFlags),
netframework.Name: NewBuilderWithFlags(netframework.NewWithFlags),
nps.Name: NewBuilderWithFlags(nps.NewWithFlags),
os.Name: NewBuilderWithFlags(os.NewWithFlags),
pagefile.Name: NewBuilderWithFlags(pagefile.NewWithFlags),
performancecounter.Name: NewBuilderWithFlags(performancecounter.NewWithFlags),
physical_disk.Name: NewBuilderWithFlags(physical_disk.NewWithFlags),
printer.Name: NewBuilderWithFlags(printer.NewWithFlags),
process.Name: NewBuilderWithFlags(process.NewWithFlags),
registry.Name: NewBuilderWithFlags(registry.NewWithFlags),
remote_fx.Name: NewBuilderWithFlags(remote_fx.NewWithFlags),
scheduled_task.Name: NewBuilderWithFlags(scheduled_task.NewWithFlags),
service.Name: NewBuilderWithFlags(service.NewWithFlags),
smb.Name: NewBuilderWithFlags(smb.NewWithFlags),
smbclient.Name: NewBuilderWithFlags(smbclient.NewWithFlags),
smtp.Name: NewBuilderWithFlags(smtp.NewWithFlags),
storage_spaces.Name: NewBuilderWithFlags(storage_spaces.NewWithFlags),
system.Name: NewBuilderWithFlags(system.NewWithFlags),
tcp.Name: NewBuilderWithFlags(tcp.NewWithFlags),
terminal_services.Name: NewBuilderWithFlags(terminal_services.NewWithFlags),
textfile.Name: NewBuilderWithFlags(textfile.NewWithFlags),
time.Name: NewBuilderWithFlags(time.NewWithFlags),
udp.Name: NewBuilderWithFlags(udp.NewWithFlags),
update.Name: NewBuilderWithFlags(update.NewWithFlags),
vmware.Name: NewBuilderWithFlags(vmware.NewWithFlags),
wmi.Name: NewBuilderWithFlags(wmi.NewWithFlags),
}
var BuildersWithFlags = func() map[string]BuilderWithFlags[Collector] {
builders := make(map[string]BuilderWithFlags[Collector], len(registrations))
for _, r := range registrations {
builders[r.name] = r.newWithFlags
}
return builders
}()
// Available returns a sorted list of available collectors.
//
+213
View File
@@ -0,0 +1,213 @@
// 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 collector
import (
"maps"
"path/filepath"
"reflect"
"slices"
"strings"
"testing"
"github.com/alecthomas/kingpin/v2"
"github.com/prometheus-community/windows_exporter/internal/collector/ad"
"github.com/prometheus-community/windows_exporter/internal/collector/adcs"
"github.com/prometheus-community/windows_exporter/internal/collector/adfs"
"github.com/prometheus-community/windows_exporter/internal/collector/cache"
"github.com/prometheus-community/windows_exporter/internal/collector/container"
"github.com/prometheus-community/windows_exporter/internal/collector/cpu"
"github.com/prometheus-community/windows_exporter/internal/collector/cpu_info"
"github.com/prometheus-community/windows_exporter/internal/collector/dfsr"
"github.com/prometheus-community/windows_exporter/internal/collector/dhcp"
"github.com/prometheus-community/windows_exporter/internal/collector/diskdrive"
"github.com/prometheus-community/windows_exporter/internal/collector/dmi"
"github.com/prometheus-community/windows_exporter/internal/collector/dns"
"github.com/prometheus-community/windows_exporter/internal/collector/exchange"
"github.com/prometheus-community/windows_exporter/internal/collector/file"
"github.com/prometheus-community/windows_exporter/internal/collector/fsrmquota"
"github.com/prometheus-community/windows_exporter/internal/collector/gpu"
"github.com/prometheus-community/windows_exporter/internal/collector/hyperv"
"github.com/prometheus-community/windows_exporter/internal/collector/iis"
"github.com/prometheus-community/windows_exporter/internal/collector/license"
"github.com/prometheus-community/windows_exporter/internal/collector/logical_disk"
"github.com/prometheus-community/windows_exporter/internal/collector/memory"
"github.com/prometheus-community/windows_exporter/internal/collector/mscluster"
"github.com/prometheus-community/windows_exporter/internal/collector/msmq"
"github.com/prometheus-community/windows_exporter/internal/collector/mssql"
"github.com/prometheus-community/windows_exporter/internal/collector/net"
"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/performancecounter"
"github.com/prometheus-community/windows_exporter/internal/collector/physical_disk"
"github.com/prometheus-community/windows_exporter/internal/collector/printer"
"github.com/prometheus-community/windows_exporter/internal/collector/process"
"github.com/prometheus-community/windows_exporter/internal/collector/registry"
"github.com/prometheus-community/windows_exporter/internal/collector/remote_fx"
"github.com/prometheus-community/windows_exporter/internal/collector/scheduled_task"
"github.com/prometheus-community/windows_exporter/internal/collector/service"
"github.com/prometheus-community/windows_exporter/internal/collector/smb"
"github.com/prometheus-community/windows_exporter/internal/collector/smbclient"
"github.com/prometheus-community/windows_exporter/internal/collector/smtp"
"github.com/prometheus-community/windows_exporter/internal/collector/storage_spaces"
"github.com/prometheus-community/windows_exporter/internal/collector/system"
"github.com/prometheus-community/windows_exporter/internal/collector/tcp"
"github.com/prometheus-community/windows_exporter/internal/collector/terminal_services"
"github.com/prometheus-community/windows_exporter/internal/collector/textfile"
"github.com/prometheus-community/windows_exporter/internal/collector/time"
"github.com/prometheus-community/windows_exporter/internal/collector/udp"
"github.com/prometheus-community/windows_exporter/internal/collector/update"
"github.com/prometheus-community/windows_exporter/internal/collector/vmware"
"github.com/prometheus-community/windows_exporter/internal/collector/wmi"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestRegistrations verifies that every place that lists the collectors agrees
// with the registrations: the flag builders, NewWithConfig, the fields and
// YAML keys of Config, ConfigDefaults and the collector documentation.
func TestRegistrations(t *testing.T) {
t.Parallel()
names := make([]string, 0, len(registrations))
for _, r := range registrations {
require.NotContains(t, names, r.name, "collector %s is registered twice", r.name)
names = append(names, r.name)
}
slices.Sort(names)
t.Run("flag builders", func(t *testing.T) {
t.Parallel()
require.Equal(t, names, Available())
app := kingpin.New("test", "test")
for name, builder := range BuildersWithFlags {
assert.Equal(t, name, builder(app).GetName(), "flag builder of collector %s", name)
}
})
t.Run("NewWithConfig", func(t *testing.T) {
t.Parallel()
collection := NewWithConfig(ConfigDefaults)
require.Equal(t, names, slices.Sorted(maps.Keys(collection.collectors)))
for name, c := range collection.collectors {
assert.Equal(t, name, c.GetName(), "NewWithConfig collector %s", name)
}
})
t.Run("Config fields", func(t *testing.T) {
t.Parallel()
var config Config
value := reflect.ValueOf(&config).Elem()
// Fields of size zero can share an address, so the type is part of
// the key. Every collector has its own configuration type.
type fieldKey struct {
addr uintptr
typ reflect.Type
}
keyOf := func(ptr reflect.Value) fieldKey {
return fieldKey{addr: ptr.Pointer(), typ: ptr.Type().Elem()}
}
fields := map[fieldKey]reflect.StructField{}
for i := range value.NumField() {
fields[keyOf(value.Field(i).Addr())] = value.Type().Field(i)
}
for _, r := range registrations {
key := keyOf(reflect.ValueOf(r.field(&config)))
field, ok := fields[key]
if !assert.True(t, ok, "collector %s has no field in Config", r.name) {
continue
}
configKey, _, _ := strings.Cut(field.Tag.Get("yaml"), ",")
assert.Equal(t, r.name, configKey, "YAML key of Config.%s", field.Name)
delete(fields, key)
}
for _, field := range fields {
assert.Fail(t, "Config field without collector registration", "Config.%s", field.Name)
}
})
t.Run("ConfigDefaults", func(t *testing.T) {
t.Parallel()
// The defaults of every collector, listed independently of the
// registrations.
defaults := map[string]any{
ad.Name: ad.ConfigDefaults, adcs.Name: adcs.ConfigDefaults, adfs.Name: adfs.ConfigDefaults,
cache.Name: cache.ConfigDefaults, container.Name: container.ConfigDefaults, cpu.Name: cpu.ConfigDefaults,
cpu_info.Name: cpu_info.ConfigDefaults, dfsr.Name: dfsr.ConfigDefaults, dhcp.Name: dhcp.ConfigDefaults,
diskdrive.Name: diskdrive.ConfigDefaults, dmi.Name: dmi.ConfigDefaults, dns.Name: dns.ConfigDefaults,
exchange.Name: exchange.ConfigDefaults, file.Name: file.ConfigDefaults, fsrmquota.Name: fsrmquota.ConfigDefaults,
gpu.Name: gpu.ConfigDefaults, hyperv.Name: hyperv.ConfigDefaults, iis.Name: iis.ConfigDefaults,
license.Name: license.ConfigDefaults, logical_disk.Name: logical_disk.ConfigDefaults, memory.Name: memory.ConfigDefaults,
mscluster.Name: mscluster.ConfigDefaults, msmq.Name: msmq.ConfigDefaults, mssql.Name: mssql.ConfigDefaults,
net.Name: net.ConfigDefaults, netframework.Name: netframework.ConfigDefaults, nps.Name: nps.ConfigDefaults,
os.Name: os.ConfigDefaults, pagefile.Name: pagefile.ConfigDefaults, performancecounter.Name: performancecounter.ConfigDefaults,
physical_disk.Name: physical_disk.ConfigDefaults, printer.Name: printer.ConfigDefaults, process.Name: process.ConfigDefaults,
registry.Name: registry.ConfigDefaults, remote_fx.Name: remote_fx.ConfigDefaults, scheduled_task.Name: scheduled_task.ConfigDefaults,
service.Name: service.ConfigDefaults, smb.Name: smb.ConfigDefaults, smbclient.Name: smbclient.ConfigDefaults,
smtp.Name: smtp.ConfigDefaults, storage_spaces.Name: storage_spaces.ConfigDefaults, system.Name: system.ConfigDefaults,
tcp.Name: tcp.ConfigDefaults, terminal_services.Name: terminal_services.ConfigDefaults, textfile.Name: textfile.ConfigDefaults,
time.Name: time.ConfigDefaults, udp.Name: udp.ConfigDefaults, update.Name: update.ConfigDefaults,
vmware.Name: vmware.ConfigDefaults, wmi.Name: wmi.ConfigDefaults,
}
require.Len(t, defaults, len(registrations))
for _, r := range registrations {
want, ok := defaults[r.name]
if !assert.True(t, ok, "collector %s has no defaults in the test", r.name) {
continue
}
got := reflect.ValueOf(r.field(&ConfigDefaults)).Elem().Interface()
assert.Equal(t, want, got, "ConfigDefaults of collector %s", r.name)
}
})
t.Run("documentation", func(t *testing.T) {
t.Parallel()
files, err := filepath.Glob(filepath.Join("..", "..", "docs", "collector.*.md"))
require.NoError(t, err)
documented := make([]string, 0, len(files))
for _, file := range files {
documented = append(documented, strings.TrimSuffix(strings.TrimPrefix(filepath.Base(file), "collector."), ".md"))
}
require.ElementsMatch(t, names, documented, "docs/collector.<name>.md files")
})
}