From 11f93d3da1232ea26d89d72753fa454a56e228d8 Mon Sep 17 00:00:00 2001 From: eduarrrd Date: Mon, 30 Sep 2024 09:40:03 +0200 Subject: [PATCH] textfile: Allow specifiying multiple directory globs. (#3135) We already support reading from multiple directories though only using globs. Now we can specify them outright. Example use case is exporting both static info on a RO FS generated during image building and traditional uses of textfiles (e.g. for R/W service metrics files) without scripting a file copy. * keep flag name for compatibility * clarify flag help text * add test case (replicating the glob one) Signed-off-by: eduarrrd --- collector/textfile.go | 22 ++++++----- collector/textfile_test.go | 75 +++++++++++++++++++++----------------- 2 files changed, 54 insertions(+), 43 deletions(-) diff --git a/collector/textfile.go b/collector/textfile.go index 6c831489..134083af 100644 --- a/collector/textfile.go +++ b/collector/textfile.go @@ -32,8 +32,8 @@ import ( ) var ( - textFileDirectory = kingpin.Flag("collector.textfile.directory", "Directory to read text files with metrics from.").Default("").String() - mtimeDesc = prometheus.NewDesc( + textFileDirectories = kingpin.Flag("collector.textfile.directory", "Directory to read text files with metrics from, supports glob matching. (repeatable)").Default("").Strings() + mtimeDesc = prometheus.NewDesc( "node_textfile_mtime_seconds", "Unixtime mtime of textfiles successfully read.", []string{"file"}, @@ -42,7 +42,7 @@ var ( ) type textFileCollector struct { - path string + paths []string // Only set for testing to get predictable output. mtime *float64 logger *slog.Logger @@ -56,7 +56,7 @@ func init() { // in the given textfile directory. func NewTextFileCollector(logger *slog.Logger) (Collector, error) { c := &textFileCollector{ - path: *textFileDirectory, + paths: *textFileDirectories, logger: logger, } return c, nil @@ -194,11 +194,15 @@ func (c *textFileCollector) Update(ch chan<- prometheus.Metric) error { metricsNamesToFiles := map[string][]string{} metricsNamesToHelpTexts := map[string][2]string{} - paths, err := filepath.Glob(c.path) - if err != nil || len(paths) == 0 { - // not glob or not accessible path either way assume single - // directory and let os.ReadDir handle it - paths = []string{c.path} + paths := []string{} + for _, glob := range c.paths { + ps, err := filepath.Glob(glob) + if err != nil || len(ps) == 0 { + // not glob or not accessible path either way assume single + // directory and let os.ReadDir handle it + ps = []string{glob} + } + paths = append(paths, ps...) } mtimes := make(map[string]time.Time) diff --git a/collector/textfile_test.go b/collector/textfile_test.go index cd08c64f..ece70b3a 100644 --- a/collector/textfile_test.go +++ b/collector/textfile_test.go @@ -52,75 +52,82 @@ func (a collectorAdapter) Collect(ch chan<- prometheus.Metric) { func TestTextfileCollector(t *testing.T) { tests := []struct { - path string - out string + paths []string + out string }{ { - path: "fixtures/textfile/no_metric_files", - out: "fixtures/textfile/no_metric_files.out", + paths: []string{"fixtures/textfile/no_metric_files"}, + out: "fixtures/textfile/no_metric_files.out", }, { - path: "fixtures/textfile/two_metric_files", - out: "fixtures/textfile/two_metric_files.out", + paths: []string{"fixtures/textfile/two_metric_files"}, + out: "fixtures/textfile/two_metric_files.out", }, { - path: "fixtures/textfile/nonexistent_path", - out: "fixtures/textfile/nonexistent_path.out", + paths: []string{"fixtures/textfile/nonexistent_path"}, + out: "fixtures/textfile/nonexistent_path.out", }, { - path: "fixtures/textfile/client_side_timestamp", - out: "fixtures/textfile/client_side_timestamp.out", + paths: []string{"fixtures/textfile/client_side_timestamp"}, + out: "fixtures/textfile/client_side_timestamp.out", }, { - path: "fixtures/textfile/different_metric_types", - out: "fixtures/textfile/different_metric_types.out", + paths: []string{"fixtures/textfile/different_metric_types"}, + out: "fixtures/textfile/different_metric_types.out", }, { - path: "fixtures/textfile/inconsistent_metrics", - out: "fixtures/textfile/inconsistent_metrics.out", + paths: []string{"fixtures/textfile/inconsistent_metrics"}, + out: "fixtures/textfile/inconsistent_metrics.out", }, { - path: "fixtures/textfile/histogram", - out: "fixtures/textfile/histogram.out", + paths: []string{"fixtures/textfile/histogram"}, + out: "fixtures/textfile/histogram.out", }, { - path: "fixtures/textfile/histogram_extra_dimension", - out: "fixtures/textfile/histogram_extra_dimension.out", + paths: []string{"fixtures/textfile/histogram_extra_dimension"}, + out: "fixtures/textfile/histogram_extra_dimension.out", }, { - path: "fixtures/textfile/summary", - out: "fixtures/textfile/summary.out", + paths: []string{"fixtures/textfile/summary"}, + out: "fixtures/textfile/summary.out", }, { - path: "fixtures/textfile/summary_extra_dimension", - out: "fixtures/textfile/summary_extra_dimension.out", + paths: []string{"fixtures/textfile/summary_extra_dimension"}, + out: "fixtures/textfile/summary_extra_dimension.out", }, { - path: "fixtures/textfile/*_extra_dimension", - out: "fixtures/textfile/glob_extra_dimension.out", + paths: []string{ + "fixtures/textfile/histogram_extra_dimension", + "fixtures/textfile/summary_extra_dimension", + }, + out: "fixtures/textfile/glob_extra_dimension.out", }, { - path: "fixtures/textfile/metrics_merge_empty_help", - out: "fixtures/textfile/metrics_merge_empty_help.out", + paths: []string{"fixtures/textfile/*_extra_dimension"}, + out: "fixtures/textfile/glob_extra_dimension.out", }, { - path: "fixtures/textfile/metrics_merge_no_help", - out: "fixtures/textfile/metrics_merge_no_help.out", + paths: []string{"fixtures/textfile/metrics_merge_empty_help"}, + out: "fixtures/textfile/metrics_merge_empty_help.out", }, { - path: "fixtures/textfile/metrics_merge_same_help", - out: "fixtures/textfile/metrics_merge_same_help.out", + paths: []string{"fixtures/textfile/metrics_merge_no_help"}, + out: "fixtures/textfile/metrics_merge_no_help.out", }, { - path: "fixtures/textfile/metrics_merge_different_help", - out: "fixtures/textfile/metrics_merge_different_help.out", + paths: []string{"fixtures/textfile/metrics_merge_same_help"}, + out: "fixtures/textfile/metrics_merge_same_help.out", + }, + { + paths: []string{"fixtures/textfile/metrics_merge_different_help"}, + out: "fixtures/textfile/metrics_merge_different_help.out", }, } for i, test := range tests { mtime := 1.0 c := &textFileCollector{ - path: test.path, + paths: test.paths, mtime: &mtime, logger: slog.New(slog.NewTextHandler(io.Discard, nil)), } @@ -146,7 +153,7 @@ func TestTextfileCollector(t *testing.T) { } if string(want) != got { - t.Fatalf("%d.%q want:\n\n%s\n\ngot:\n\n%s", i, test.path, string(want), got) + t.Fatalf("%d.%q want:\n\n%s\n\ngot:\n\n%s", i, test.paths, string(want), got) } } }