Skip to content

Commit 9f925e6

Browse files
Add filesystem UUID based filter
Add filesystem UUID based filter to filesystem collector. Signed-off-by: erjavaskivuori <[email protected]>
1 parent 2f98016 commit 9f925e6

File tree

4 files changed

+113
-17
lines changed

4 files changed

+113
-17
lines changed

collector/filesystem_common.go

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,26 @@ var (
6767
"Regexp of filesystem types to exclude for filesystem collector. (mutually exclusive to fs-types-exclude)",
6868
).String()
6969

70-
filesystemLabelNames = []string{"device", "mountpoint", "fstype", "device_error"}
70+
fsUUIDsExcludeSet bool
71+
fsUUIDsExclude = kingpin.Flag(
72+
"collector.filesystem.fs-uuids-exclude",
73+
"Regexp of filesystem UUIDs to exclude for filesystem collector. (mutually exclusive to fs-uuids-include)",
74+
).Default("").PreAction(func(c *kingpin.ParseContext) error {
75+
fsUUIDsExcludeSet = true
76+
return nil
77+
}).String()
78+
fsUUIDsInclude = kingpin.Flag(
79+
"collector.filesystem.fs-uuids-include",
80+
"Regexp of filesystem UUIDs to include for filesystem collector. (mutually exclusive to fs-uuids-exclude)",
81+
).String()
82+
83+
filesystemLabelNames = []string{"device", "mountpoint", "fstype", "fsuuid", "device_error"}
7184
)
7285

7386
type filesystemCollector struct {
7487
mountPointFilter deviceFilter
7588
fsTypeFilter deviceFilter
89+
fsUUIDFilter deviceFilter
7690
sizeDesc, freeDesc, availDesc *prometheus.Desc
7791
filesDesc, filesFreeDesc *prometheus.Desc
7892
purgeableDesc *prometheus.Desc
@@ -82,7 +96,7 @@ type filesystemCollector struct {
8296
}
8397

8498
type filesystemLabels struct {
85-
device, mountPoint, fsType, mountOptions, superOptions, deviceError, major, minor string
99+
device, mountPoint, fsType, fsUUID, mountOptions, superOptions, deviceError, major, minor string
86100
}
87101

88102
type filesystemStats struct {
@@ -166,9 +180,15 @@ func NewFilesystemCollector(logger *slog.Logger) (Collector, error) {
166180
return nil, fmt.Errorf("unable to parse fs types filter flags: %w", err)
167181
}
168182

183+
fsUUIDFilter, err := newFSUUIDFilter(logger)
184+
if err != nil {
185+
return nil, fmt.Errorf("unable to parse fs UUIDs filter flags: %w", err)
186+
}
187+
169188
return &filesystemCollector{
170189
mountPointFilter: mountPointFilter,
171190
fsTypeFilter: fsTypeFilter,
191+
fsUUIDFilter: fsUUIDFilter,
172192
sizeDesc: sizeDesc,
173193
freeDesc: freeDesc,
174194
availDesc: availDesc,
@@ -197,11 +217,11 @@ func (c *filesystemCollector) Update(ch chan<- prometheus.Metric) error {
197217

198218
ch <- prometheus.MustNewConstMetric(
199219
c.deviceErrorDesc, prometheus.GaugeValue,
200-
s.deviceError, s.labels.device, s.labels.mountPoint, s.labels.fsType, s.labels.deviceError,
220+
s.deviceError, s.labels.device, s.labels.mountPoint, s.labels.fsType, s.labels.fsUUID, s.labels.deviceError,
201221
)
202222
ch <- prometheus.MustNewConstMetric(
203223
c.roDesc, prometheus.GaugeValue,
204-
s.ro, s.labels.device, s.labels.mountPoint, s.labels.fsType, s.labels.deviceError,
224+
s.ro, s.labels.device, s.labels.mountPoint, s.labels.fsType, s.labels.fsUUID, s.labels.deviceError,
205225
)
206226

207227
if s.deviceError > 0 {
@@ -210,23 +230,23 @@ func (c *filesystemCollector) Update(ch chan<- prometheus.Metric) error {
210230

211231
ch <- prometheus.MustNewConstMetric(
212232
c.sizeDesc, prometheus.GaugeValue,
213-
s.size, s.labels.device, s.labels.mountPoint, s.labels.fsType, s.labels.deviceError,
233+
s.size, s.labels.device, s.labels.mountPoint, s.labels.fsType, s.labels.fsUUID, s.labels.deviceError,
214234
)
215235
ch <- prometheus.MustNewConstMetric(
216236
c.freeDesc, prometheus.GaugeValue,
217-
s.free, s.labels.device, s.labels.mountPoint, s.labels.fsType, s.labels.deviceError,
237+
s.free, s.labels.device, s.labels.mountPoint, s.labels.fsType, s.labels.fsUUID, s.labels.deviceError,
218238
)
219239
ch <- prometheus.MustNewConstMetric(
220240
c.availDesc, prometheus.GaugeValue,
221-
s.avail, s.labels.device, s.labels.mountPoint, s.labels.fsType, s.labels.deviceError,
241+
s.avail, s.labels.device, s.labels.mountPoint, s.labels.fsType, s.labels.fsUUID, s.labels.deviceError,
222242
)
223243
ch <- prometheus.MustNewConstMetric(
224244
c.filesDesc, prometheus.GaugeValue,
225-
s.files, s.labels.device, s.labels.mountPoint, s.labels.fsType, s.labels.deviceError,
245+
s.files, s.labels.device, s.labels.mountPoint, s.labels.fsType, s.labels.fsUUID, s.labels.deviceError,
226246
)
227247
ch <- prometheus.MustNewConstMetric(
228248
c.filesFreeDesc, prometheus.GaugeValue,
229-
s.filesFree, s.labels.device, s.labels.mountPoint, s.labels.fsType, s.labels.deviceError,
249+
s.filesFree, s.labels.device, s.labels.mountPoint, s.labels.fsType, s.labels.fsUUID, s.labels.deviceError,
230250
)
231251
ch <- prometheus.MustNewConstMetric(
232252
c.mountInfoDesc, prometheus.GaugeValue,
@@ -235,7 +255,7 @@ func (c *filesystemCollector) Update(ch chan<- prometheus.Metric) error {
235255
if s.purgeable >= 0 {
236256
ch <- prometheus.MustNewConstMetric(
237257
c.purgeableDesc, prometheus.GaugeValue,
238-
s.purgeable, s.labels.device, s.labels.mountPoint, s.labels.fsType, s.labels.deviceError,
258+
s.purgeable, s.labels.device, s.labels.mountPoint, s.labels.fsType, s.labels.fsUUID, s.labels.deviceError,
239259
)
240260
}
241261
}
@@ -299,3 +319,22 @@ func newFSTypeFilter(logger *slog.Logger) (deviceFilter, error) {
299319

300320
return newDeviceFilter(*fsTypesExclude, *fsTypesInclude), nil
301321
}
322+
323+
func newFSUUIDFilter(logger *slog.Logger) (deviceFilter, error) {
324+
if *fsUUIDsInclude != "" && !fsUUIDsExcludeSet {
325+
logger.Debug("fs-uuids-exclude flag not set when fs-uuids-include flag is set, assuming include is desired")
326+
*fsUUIDsExclude = ""
327+
}
328+
if *fsUUIDsExclude != "" && *fsUUIDsInclude != "" {
329+
return deviceFilter{}, errors.New("--collector.filesystem.fs-uuids-exclude and --collector.filesystem.fs-uuids-include are mutually exclusive")
330+
}
331+
332+
if *fsUUIDsExclude != "" {
333+
logger.Info("Parsed flag --collector.filesystem.fs-uuids-exclude", "flag", *fsUUIDsExclude)
334+
}
335+
if *fsUUIDsInclude != "" {
336+
logger.Info("Parsed flag --collector.filesystem.fs-uuids-include", "flag", *fsUUIDsInclude)
337+
}
338+
339+
return newDeviceFilter(*fsUUIDsExclude, *fsUUIDsInclude), nil
340+
}

collector/filesystem_linux.go

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,12 @@ import (
2121
"errors"
2222
"fmt"
2323
"io"
24+
"io/fs"
2425
"log/slog"
2526
"os"
2627
"slices"
2728
"strconv"
29+
"path/filepath"
2830
"strings"
2931
"sync"
3032
"time"
@@ -84,6 +86,11 @@ func (c *filesystemCollector) GetStats() ([]filesystemStats, error) {
8486
continue
8587
}
8688

89+
if c.fsUUIDFilter.ignored(labels.fsUUID) {
90+
c.logger.Debug("Ignoring fs UUID", "uuid", labels.fsUUID)
91+
continue
92+
}
93+
8794
stuckMountsMtx.Lock()
8895
if _, ok := stuckMounts[labels.mountPoint]; ok {
8996
labels.deviceError = "mountpoint timeout"
@@ -187,10 +194,47 @@ func mountPointDetails(logger *slog.Logger) ([]filesystemLabels, error) {
187194
}
188195
defer file.Close()
189196

190-
return parseFilesystemLabels(file)
197+
UUIDMap := buildUUIDMap(logger)
198+
199+
return parseFilesystemLabels(file, UUIDMap)
200+
}
201+
202+
// buildUUIDMap builds a map of device major:minor numbers to their filesystem UUIDs.
203+
func buildUUIDMap(logger *slog.Logger) map[string]string {
204+
UUIDMap := map[string]string{}
205+
dir := devDiskFilePath("by-uuid")
206+
_ = filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
207+
if err != nil || d.IsDir() {
208+
logger.Debug("Skipping non-file entry", "path", path, "err", err)
209+
return nil
210+
}
211+
uuid := d.Name()
212+
target, err := os.Readlink(path)
213+
if err != nil {
214+
if t, err := filepath.EvalSymlinks(path); err == nil {
215+
target = t
216+
} else {
217+
logger.Debug("Failed to read symlink", "path", path, "err", err)
218+
return nil
219+
}
220+
}
221+
if !filepath.IsAbs(target) {
222+
target = filepath.Join(filepath.Dir(path), target)
223+
}
224+
var stat unix.Stat_t
225+
if err := unix.Stat(target, &stat); err != nil {
226+
logger.Debug("Failed to stat device", "path", path, "err", err)
227+
return nil
228+
}
229+
maj := unix.Major(uint64(stat.Rdev))
230+
min := unix.Minor(uint64(stat.Rdev))
231+
UUIDMap[fmt.Sprintf("%d:%d", maj, min)] = uuid
232+
return nil
233+
})
234+
return UUIDMap
191235
}
192236

193-
func parseFilesystemLabels(r io.Reader) ([]filesystemLabels, error) {
237+
func parseFilesystemLabels(r io.Reader, UUIDMap map[string]string) ([]filesystemLabels, error) {
194238
var filesystems []filesystemLabels
195239

196240
scanner := bufio.NewScanner(r)
@@ -212,6 +256,11 @@ func parseFilesystemLabels(r io.Reader) ([]filesystemLabels, error) {
212256
m++
213257
}
214258

259+
fsUUID, ok := UUIDMap[fmt.Sprintf("%d:%d", major, minor)]
260+
if !ok {
261+
fsUUID = ""
262+
}
263+
215264
// Ensure we handle the translation of \040 and \011
216265
// as per fstab(5).
217266
parts[4] = strings.ReplaceAll(parts[4], "\\040", " ")
@@ -221,6 +270,7 @@ func parseFilesystemLabels(r io.Reader) ([]filesystemLabels, error) {
221270
device: parts[m+3],
222271
mountPoint: rootfsStripPrefix(parts[4]),
223272
fsType: parts[m+2],
273+
fsUUID: fsUUID,
224274
mountOptions: parts[5],
225275
superOptions: parts[10],
226276
major: strconv.Itoa(major),

collector/filesystem_linux_test.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,20 @@ import (
2727

2828
func Test_parseFilesystemLabelsError(t *testing.T) {
2929
tests := []struct {
30-
name string
31-
in string
30+
name string
31+
in string
32+
uuids map[string]string
3233
}{
3334
{
34-
name: "too few fields",
35-
in: "hello world",
35+
name: "too few fields",
36+
in: "hello world",
37+
uuids: map[string]string{},
3638
},
3739
}
3840

3941
for _, tt := range tests {
4042
t.Run(tt.name, func(t *testing.T) {
41-
if _, err := parseFilesystemLabels(strings.NewReader(tt.in)); err == nil {
43+
if _, err := parseFilesystemLabels(strings.NewReader(tt.in), tt.uuids); err == nil {
4244
t.Fatal("expected an error, but none occurred")
4345
}
4446
})

collector/paths.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ var (
2727
sysPath = kingpin.Flag("path.sysfs", "sysfs mountpoint.").Default("/sys").String()
2828
rootfsPath = kingpin.Flag("path.rootfs", "rootfs mountpoint.").Default("/").String()
2929
udevDataPath = kingpin.Flag("path.udev.data", "udev data path.").Default("/run/udev/data").String()
30+
devDiskPath = kingpin.Flag("path.dev.disk", "path to /dev/disk").Default("/dev/disk").String()
3031
)
3132

3233
func procFilePath(name string) string {
@@ -45,6 +46,10 @@ func udevDataFilePath(name string) string {
4546
return filepath.Join(*udevDataPath, name)
4647
}
4748

49+
func devDiskFilePath(name string) string {
50+
return filepath.Join(*devDiskPath, name)
51+
}
52+
4853
func rootfsStripPrefix(path string) string {
4954
if *rootfsPath == "/" {
5055
return path

0 commit comments

Comments
 (0)