diff --git a/docs/collector.file.md b/docs/collector.file.md new file mode 100644 index 000000000..be7258954 --- /dev/null +++ b/docs/collector.file.md @@ -0,0 +1,40 @@ +# file collector + +The file collector exposes modified timestamps and file size of files in the filesystem. + +The collector + +||| +-|- +Metric name prefix | `file` +Enabled by default? | No + +## Flags + +### `--collector.file.file-patterns` +Comma-separated list of file patterns. Each pattern is a glob pattern that can contain `*`, `?`, and `**` (recursive). +See https://github.com/bmatcuk/doublestar#patterns for an extended description of the pattern syntax. + +## Metrics + +| Name | Description | Type | Labels | +|----------------------------------------|------------------------|-------|--------| +| `windows_file_mtime_timestamp_seconds` | File modification time | gauge | `file` | +| `windows_file_size_bytes` | File size | gauge | `file` | + +### Example metric + +``` +# HELP windows_file_mtime_timestamp_seconds File modification time +# TYPE windows_file_mtime_timestamp_seconds gauge +windows_file_mtime_timestamp_seconds{file="C:\\Users\\admin\\Desktop\\Dashboard.lnk"} 1.726434517e+09 +# HELP windows_file_size_bytes File size +# TYPE windows_file_size_bytes gauge +windows_file_size_bytes{file="C:\\Users\\admin\\Desktop\\Dashboard.lnk"} 123 +``` + +## Useful queries +_This collector does not yet have any useful queries added, we would appreciate your help adding them!_ + +## Alerting examples +_This collector does not yet have alerting examples, we would appreciate your help adding them!_ diff --git a/internal/collector/file/file.go b/internal/collector/file/file.go new file mode 100644 index 000000000..f1b91c4fe --- /dev/null +++ b/internal/collector/file/file.go @@ -0,0 +1,188 @@ +// 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 file + +import ( + "fmt" + "io/fs" + "log/slog" + "os" + "path/filepath" + "strings" + "sync" + + "github.com/alecthomas/kingpin/v2" + "github.com/bmatcuk/doublestar/v4" + "github.com/prometheus-community/windows_exporter/internal/mi" + "github.com/prometheus-community/windows_exporter/internal/types" + "github.com/prometheus/client_golang/prometheus" +) + +const Name = "file" + +type Config struct { + FilePatterns []string `yaml:"file-patterns"` +} + +//nolint:gochecknoglobals +var ConfigDefaults = Config{ + FilePatterns: []string{}, +} + +// A Collector is a Prometheus Collector for collecting file times. +type Collector struct { + config Config + + logger *slog.Logger + fileMTime *prometheus.Desc + fileSize *prometheus.Desc +} + +func New(config *Config) *Collector { + if config == nil { + config = &ConfigDefaults + } + + if config.FilePatterns == nil { + config.FilePatterns = ConfigDefaults.FilePatterns + } + + c := &Collector{ + config: *config, + } + + return c +} + +func NewWithFlags(app *kingpin.Application) *Collector { + c := &Collector{ + config: ConfigDefaults, + } + c.config.FilePatterns = make([]string, 0) + + app.Flag( + "collector.file.file-patterns", + "Comma-separated list of file patterns. Each pattern is a glob pattern that can contain `*`, `?`, and `**` (recursive). See https://github.com/bmatcuk/doublestar#patterns", + ).Default(strings.Join(ConfigDefaults.FilePatterns, ",")).StringsVar(&c.config.FilePatterns) + + return c +} + +func (c *Collector) GetName() string { + return Name +} + +func (c *Collector) Close() error { + return nil +} + +func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error { + c.logger = logger.With(slog.String("collector", Name)) + + c.logger.Info("file collector is in an experimental state! It may subject to change.") + + c.fileMTime = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "mtime_timestamp_seconds"), + "File modification time", + []string{"file"}, + nil, + ) + + c.fileSize = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "size_bytes"), + "File size", + []string{"file"}, + nil, + ) + + for _, filePattern := range c.config.FilePatterns { + basePath, pattern := doublestar.SplitPattern(filePattern) + + _, err := doublestar.Glob(os.DirFS(basePath), pattern, doublestar.WithFilesOnly()) + if err != nil { + return fmt.Errorf("invalid glob pattern: %w", err) + } + } + + return nil +} + +// Collect sends the metric values for each metric +// to the provided prometheus Metric channel. +func (c *Collector) Collect(ch chan<- prometheus.Metric) error { + wg := sync.WaitGroup{} + + for _, filePattern := range c.config.FilePatterns { + wg.Add(1) + + go func(filePattern string) { + defer wg.Done() + + if err := c.collectGlobFilePath(ch, filePattern); err != nil { + c.logger.Error("failed collecting metrics for filepath", + slog.String("filepath", filePattern), + slog.Any("err", err), + ) + } + }(filePattern) + } + + wg.Wait() + + return nil +} + +func (c *Collector) collectGlobFilePath(ch chan<- prometheus.Metric, filePattern string) error { + basePath, pattern := doublestar.SplitPattern(filepath.ToSlash(filePattern)) + basePathFS := os.DirFS(basePath) + + err := doublestar.GlobWalk(basePathFS, pattern, func(path string, d fs.DirEntry) error { + filePath := filepath.Join(basePath, path) + + fileInfo, err := os.Stat(filePath) + if err != nil { + c.logger.Warn("failed to state file", + slog.String("file", filePath), + slog.Any("err", err), + ) + + return nil + } + + ch <- prometheus.MustNewConstMetric( + c.fileMTime, + prometheus.GaugeValue, + float64(fileInfo.ModTime().UTC().UnixMicro())/1e6, + filePath, + ) + + ch <- prometheus.MustNewConstMetric( + c.fileSize, + prometheus.GaugeValue, + float64(fileInfo.Size()), + filePath, + ) + + return nil + }, doublestar.WithFilesOnly(), doublestar.WithCaseInsensitive()) + if err != nil { + return fmt.Errorf("failed to glob: %w", err) + } + + return nil +} diff --git a/internal/collector/file/file_test.go b/internal/collector/file/file_test.go new file mode 100644 index 000000000..40c97298d --- /dev/null +++ b/internal/collector/file/file_test.go @@ -0,0 +1,35 @@ +// 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 file_test + +import ( + "testing" + + "github.com/prometheus-community/windows_exporter/internal/collector/file" + "github.com/prometheus-community/windows_exporter/internal/utils/testutils" +) + +func BenchmarkCollector(b *testing.B) { + testutils.FuncBenchmarkCollector(b, file.Name, file.NewWithFlags) +} + +func TestCollector(t *testing.T) { + testutils.TestCollector(t, file.New, &file.Config{ + FilePatterns: []string{"*.*"}, + }) +} diff --git a/pkg/collector/collection.go b/pkg/collector/collection.go index 17839c410..528826449 100644 --- a/pkg/collector/collection.go +++ b/pkg/collector/collection.go @@ -40,6 +40,7 @@ import ( "github.com/prometheus-community/windows_exporter/internal/collector/diskdrive" "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/filetime" "github.com/prometheus-community/windows_exporter/internal/collector/fsrmquota" "github.com/prometheus-community/windows_exporter/internal/collector/gpu" @@ -111,6 +112,7 @@ func NewWithConfig(config Config) *Collection { collectors[dns.Name] = dns.New(&config.DNS) collectors[exchange.Name] = exchange.New(&config.Exchange) collectors[filetime.Name] = filetime.New(&config.Filetime) + 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) diff --git a/pkg/collector/config.go b/pkg/collector/config.go index b8419fde7..6eb316bcd 100644 --- a/pkg/collector/config.go +++ b/pkg/collector/config.go @@ -30,6 +30,7 @@ import ( "github.com/prometheus-community/windows_exporter/internal/collector/diskdrive" "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/filetime" "github.com/prometheus-community/windows_exporter/internal/collector/fsrmquota" "github.com/prometheus-community/windows_exporter/internal/collector/gpu" @@ -81,6 +82,7 @@ type Config struct { DNS dns.Config `yaml:"dns"` Exchange exchange.Config `yaml:"exchange"` Filetime filetime.Config `yaml:"filetime"` + File file.Config `yaml:"file"` Fsrmquota fsrmquota.Config `yaml:"fsrmquota"` GPU gpu.Config `yaml:"gpu"` HyperV hyperv.Config `yaml:"hyperv"` diff --git a/pkg/collector/map.go b/pkg/collector/map.go index 70e894409..da22aafda 100644 --- a/pkg/collector/map.go +++ b/pkg/collector/map.go @@ -34,6 +34,7 @@ import ( "github.com/prometheus-community/windows_exporter/internal/collector/diskdrive" "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/filetime" "github.com/prometheus-community/windows_exporter/internal/collector/fsrmquota" "github.com/prometheus-community/windows_exporter/internal/collector/gpu" @@ -92,6 +93,7 @@ var BuildersWithFlags = map[string]BuilderWithFlags[Collector]{ dns.Name: NewBuilderWithFlags(dns.NewWithFlags), exchange.Name: NewBuilderWithFlags(exchange.NewWithFlags), filetime.Name: NewBuilderWithFlags(filetime.NewWithFlags), + file.Name: NewBuilderWithFlags(file.NewWithFlags), fsrmquota.Name: NewBuilderWithFlags(fsrmquota.NewWithFlags), gpu.Name: NewBuilderWithFlags(gpu.NewWithFlags), hyperv.Name: NewBuilderWithFlags(hyperv.NewWithFlags),