Skip to content

Commit 6a9856f

Browse files
Add filesystem UUID based filter
Add filesystem UUID based filter to filesystem collector. Signed-off-by: erjavaskivuori <[email protected]>
1 parent be19d53 commit 6a9856f

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, options, deviceError, major, minor string
99+
device, mountPoint, fsType, fsUUID, options, 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,8 +21,10 @@ import (
2121
"errors"
2222
"fmt"
2323
"io"
24+
"io/fs"
2425
"log/slog"
2526
"os"
27+
"path/filepath"
2628
"strings"
2729
"sync"
2830
"time"
@@ -82,6 +84,11 @@ func (c *filesystemCollector) GetStats() ([]filesystemStats, error) {
8284
continue
8385
}
8486

87+
if c.fsUUIDFilter.ignored(labels.fsUUID) {
88+
c.logger.Debug("Ignoring fs UUID", "uuid", labels.fsUUID)
89+
continue
90+
}
91+
8592
stuckMountsMtx.Lock()
8693
if _, ok := stuckMounts[labels.mountPoint]; ok {
8794
labels.deviceError = "mountpoint timeout"
@@ -188,10 +195,47 @@ func mountPointDetails(logger *slog.Logger) ([]filesystemLabels, error) {
188195
}
189196
defer file.Close()
190197

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

194-
func parseFilesystemLabels(r io.Reader) ([]filesystemLabels, error) {
238+
func parseFilesystemLabels(r io.Reader, UUIDMap map[string]string) ([]filesystemLabels, error) {
195239
var filesystems []filesystemLabels
196240

197241
scanner := bufio.NewScanner(r)
@@ -213,6 +257,11 @@ func parseFilesystemLabels(r io.Reader) ([]filesystemLabels, error) {
213257
m++
214258
}
215259

260+
fsUUID, ok := UUIDMap[fmt.Sprintf("%d:%d", major, minor)]
261+
if !ok {
262+
fsUUID = ""
263+
}
264+
216265
// Ensure we handle the translation of \040 and \011
217266
// as per fstab(5).
218267
parts[4] = strings.ReplaceAll(parts[4], "\\040", " ")
@@ -222,6 +271,7 @@ func parseFilesystemLabels(r io.Reader) ([]filesystemLabels, error) {
222271
device: parts[m+3],
223272
mountPoint: rootfsStripPrefix(parts[4]),
224273
fsType: parts[m+2],
274+
fsUUID: fsUUID,
225275
options: parts[5],
226276
major: fmt.Sprint(major),
227277
minor: fmt.Sprint(minor),

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)