mirror of
https://github.com/prometheus-community/windows_exporter.git
synced 2026-10-17 01:59:04 +02:00
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:
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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.
|
||||
//
|
||||
|
||||
@@ -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")
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user