Skip to content

Commit e1ae6ae

Browse files
authored
filesystem: Refactor mountinfo parsing (#3452)
* filesystem: Refactor mountinfo parsing Migrate mountinfo parsing to procfs library. This fixes incorrect parsing introduced by PR #3387. Fixes: #3450 Signed-off-by: Ben Kochie <[email protected]> * Update fixtures for new infiniband procfs feature. Signed-off-by: Ben Kochie <[email protected]> --------- Signed-off-by: Ben Kochie <[email protected]>
1 parent cc2212b commit e1ae6ae

File tree

6 files changed

+64
-42
lines changed

6 files changed

+64
-42
lines changed

collector/filesystem_linux.go

Lines changed: 34 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,9 @@
1717
package collector
1818

1919
import (
20-
"bufio"
20+
"bytes"
2121
"errors"
2222
"fmt"
23-
"io"
2423
"log/slog"
2524
"os"
2625
"slices"
@@ -31,6 +30,8 @@ import (
3130

3231
"github.com/alecthomas/kingpin/v2"
3332
"golang.org/x/sys/unix"
33+
34+
"github.com/prometheus/procfs"
3435
)
3536

3637
const (
@@ -179,60 +180,51 @@ func stuckMountWatcher(mountPoint string, success chan struct{}, logger *slog.Lo
179180
}
180181

181182
func mountPointDetails(logger *slog.Logger) ([]filesystemLabels, error) {
182-
file, err := os.Open(procFilePath("1/mountinfo"))
183+
fs, err := procfs.NewFS(*procPath)
184+
if err != nil {
185+
return nil, fmt.Errorf("failed to open procfs: %w", err)
186+
}
187+
mountInfo, err := fs.GetProcMounts(1)
183188
if errors.Is(err, os.ErrNotExist) {
184189
// Fallback to `/proc/self/mountinfo` if `/proc/1/mountinfo` is missing due hidepid.
185190
logger.Debug("Reading root mounts failed, falling back to self mounts", "err", err)
186-
file, err = os.Open(procFilePath("self/mountinfo"))
191+
mountInfo, err = fs.GetMounts()
187192
}
188193
if err != nil {
189194
return nil, err
190195
}
191-
defer file.Close()
192196

193-
return parseFilesystemLabels(file)
197+
return parseFilesystemLabels(mountInfo)
194198
}
195199

196-
func parseFilesystemLabels(r io.Reader) ([]filesystemLabels, error) {
200+
func parseFilesystemLabels(mountInfo []*procfs.MountInfo) ([]filesystemLabels, error) {
197201
var filesystems []filesystemLabels
198202

199-
scanner := bufio.NewScanner(r)
200-
for scanner.Scan() {
201-
parts := strings.Fields(scanner.Text())
202-
203-
if len(parts) < 11 {
204-
return nil, fmt.Errorf("malformed mount point information: %q", scanner.Text())
205-
}
206-
203+
for _, mount := range mountInfo {
207204
major, minor := 0, 0
208-
_, err := fmt.Sscanf(parts[2], "%d:%d", &major, &minor)
205+
_, err := fmt.Sscanf(mount.MajorMinorVer, "%d:%d", &major, &minor)
209206
if err != nil {
210-
return nil, fmt.Errorf("malformed mount point information: %q", scanner.Text())
211-
}
212-
213-
m := 5
214-
for parts[m+1] != "-" {
215-
m++
207+
return nil, fmt.Errorf("malformed mount point MajorMinorVer: %q", mount.MajorMinorVer)
216208
}
217209

218210
// Ensure we handle the translation of \040 and \011
219211
// as per fstab(5).
220-
parts[4] = strings.ReplaceAll(parts[4], "\\040", " ")
221-
parts[4] = strings.ReplaceAll(parts[4], "\\011", "\t")
212+
mount.MountPoint = strings.ReplaceAll(mount.MountPoint, "\\040", " ")
213+
mount.MountPoint = strings.ReplaceAll(mount.MountPoint, "\\011", "\t")
222214

223215
filesystems = append(filesystems, filesystemLabels{
224-
device: parts[m+3],
225-
mountPoint: rootfsStripPrefix(parts[4]),
226-
fsType: parts[m+2],
227-
mountOptions: parts[5],
228-
superOptions: parts[10],
216+
device: mount.Source,
217+
mountPoint: rootfsStripPrefix(mount.MountPoint),
218+
fsType: mount.FSType,
219+
mountOptions: mountOptionsString(mount.Options),
220+
superOptions: mountOptionsString(mount.SuperOptions),
229221
major: strconv.Itoa(major),
230222
minor: strconv.Itoa(minor),
231223
deviceError: "",
232224
})
233225
}
234226

235-
return filesystems, scanner.Err()
227+
return filesystems, nil
236228
}
237229

238230
// see https://github.com/prometheus/node_exporter/issues/3157#issuecomment-2422761187
@@ -244,3 +236,15 @@ func isFilesystemReadOnly(labels filesystemLabels) bool {
244236

245237
return false
246238
}
239+
240+
func mountOptionsString(m map[string]string) string {
241+
b := new(bytes.Buffer)
242+
for key, value := range m {
243+
if value == "" {
244+
fmt.Fprintf(b, "%s", key)
245+
} else {
246+
fmt.Fprintf(b, "%s=%s", key, value)
247+
}
248+
}
249+
return b.String()
250+
}

collector/filesystem_linux_test.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,26 +19,31 @@ package collector
1919
import (
2020
"io"
2121
"log/slog"
22-
"strings"
2322
"testing"
2423

2524
"github.com/alecthomas/kingpin/v2"
25+
26+
"github.com/prometheus/procfs"
2627
)
2728

2829
func Test_parseFilesystemLabelsError(t *testing.T) {
2930
tests := []struct {
3031
name string
31-
in string
32+
in []*procfs.MountInfo
3233
}{
3334
{
34-
name: "too few fields",
35-
in: "hello world",
35+
name: "malformed Major:Minor",
36+
in: []*procfs.MountInfo{
37+
{
38+
MajorMinorVer: "nope",
39+
},
40+
},
3641
},
3742
}
3843

3944
for _, tt := range tests {
4045
t.Run(tt.name, func(t *testing.T) {
41-
if _, err := parseFilesystemLabels(strings.NewReader(tt.in)); err == nil {
46+
if _, err := parseFilesystemLabels(tt.in); err == nil {
4247
t.Fatal("expected an error, but none occurred")
4348
}
4449
})
@@ -120,6 +125,7 @@ func TestMountPointDetails(t *testing.T) {
120125
"/run/user/1000/gvfs": "",
121126
"/var/lib/kubelet/plugins/kubernetes.io/vsphere-volume/mounts/[vsanDatastore] bafb9e5a-8856-7e6c-699c-801844e77a4a/kubernetes-dynamic-pvc-3eba5bba-48a3-11e8-89ab-005056b92113.vmdk": "",
122127
"/var/lib/kubelet/plugins/kubernetes.io/vsphere-volume/mounts/[vsanDatastore] bafb9e5a-8856-7e6c-699c-801844e77a4a/kubernetes-dynamic-pvc-3eba5bba-48a3-11e8-89ab-005056b92113.vmdk": "",
128+
"/var/lib/containers/storage/overlay": "",
123129
}
124130

125131
filesystems, err := mountPointDetails(slog.New(slog.NewTextHandler(io.Discard, nil)))

collector/fixtures/proc/1/mountinfo

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,4 @@
2929
3147 3002 0:81 / /run/user/1000/gvfs rw,nosuid,nodev,relatime shared:1290 - fuse.gvfsd-fuse gvfsd-fuse rw,user_id=1000,group_id=1000
3030
3148 3003 260:0 / /var/lib/kubelet/plugins/kubernetes.io/vsphere-volume/mounts/[vsanDatastore]\040bafb9e5a-8856-7e6c-699c-801844e77a4a/kubernetes-dynamic-pvc-3eba5bba-48a3-11e8-89ab-005056b92113.vmdk rw,relatime shared:31 - ext4 /dev/sda rw,data=ordered
3131
3149 3004 260:0 / /var/lib/kubelet/plugins/kubernetes.io/vsphere-volume/mounts/[vsanDatastore]\011bafb9e5a-8856-7e6c-699c-801844e77a4a/kubernetes-dynamic-pvc-3eba5bba-48a3-11e8-89ab-005056b92113.vmdk rw,relatime shared:32 - ext4 /dev/sda rw,data=ordered
32+
1128 67 253:0 /var/lib/containers/storage/overlay /var/lib/containers/storage/overlay rw,relatime - xfs /dev/mapper/rhel-root rw,seclabel,attr2,inode64,logbufs=8,logbsize=32k,noquota

collector/fixtures/sys.ttar

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1414,6 +1414,11 @@ Lines: 1
14141414
N/A (no PMA)
14151415
Mode: 644
14161416
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1417+
Path: sys/class/infiniband/i40iw0/ports/1/link_layer
1418+
Lines: 1
1419+
InfiniBand
1420+
Mode: 664
1421+
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
14171422
Path: sys/class/infiniband/i40iw0/ports/1/phys_state
14181423
Lines: 1
14191424
5: LinkUp
@@ -1579,6 +1584,11 @@ Lines: 1
15791584
0
15801585
Mode: 644
15811586
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1587+
Path: sys/class/infiniband/mlx4_0/ports/1/link_layer
1588+
Lines: 1
1589+
InfiniBand
1590+
Mode: 664
1591+
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
15821592
Path: sys/class/infiniband/mlx4_0/ports/1/phys_state
15831593
Lines: 1
15841594
5: LinkUp
@@ -1683,6 +1693,11 @@ Lines: 1
16831693
0
16841694
Mode: 644
16851695
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1696+
Path: sys/class/infiniband/mlx4_0/ports/2/link_layer
1697+
Lines: 1
1698+
InfiniBand
1699+
Mode: 664
1700+
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
16861701
Path: sys/class/infiniband/mlx4_0/ports/2/phys_state
16871702
Lines: 1
16881703
5: LinkUp
@@ -10297,7 +10312,3 @@ Lines: 1
1029710312
20
1029810313
Mode: 644
1029910314
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
10300-
Path: sys/.unpacked
10301-
Lines: 0
10302-
Mode: 644
10303-
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ require (
2525
github.com/prometheus/client_model v0.6.2
2626
github.com/prometheus/common v0.67.1
2727
github.com/prometheus/exporter-toolkit v0.14.1
28-
github.com/prometheus/procfs v0.18.0
28+
github.com/prometheus/procfs v0.19.0
2929
github.com/safchain/ethtool v0.6.2
3030
golang.org/x/exp v0.0.0-20250911091902-df9299821621
3131
golang.org/x/sys v0.37.0

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,8 @@ github.com/prometheus/common v0.67.1 h1:OTSON1P4DNxzTg4hmKCc37o4ZAZDv0cfXLkOt0oE
8080
github.com/prometheus/common v0.67.1/go.mod h1:RpmT9v35q2Y+lsieQsdOh5sXZ6ajUGC8NjZAmr8vb0Q=
8181
github.com/prometheus/exporter-toolkit v0.14.1 h1:uKPE4ewweVRWFainwvAcHs3uw15pjw2dk3I7b+aNo9o=
8282
github.com/prometheus/exporter-toolkit v0.14.1/go.mod h1:di7yaAJiaMkcjcz48f/u4yRPwtyuxTU5Jr4EnM2mhtQ=
83-
github.com/prometheus/procfs v0.18.0 h1:2QTA9cKdznfYJz7EDaa7IiJobHuV7E1WzeBwcrhk0ao=
84-
github.com/prometheus/procfs v0.18.0/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
83+
github.com/prometheus/procfs v0.19.0 h1:2gU9KiEMZUhDokz1/0GToOjT7ljqxHi+GhEjk9UUMgU=
84+
github.com/prometheus/procfs v0.19.0/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
8585
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
8686
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
8787
github.com/safchain/ethtool v0.6.2 h1:O3ZPFAKEUEfbtE6J/feEe2Ft7dIJ2Sy8t4SdMRiIMHY=

0 commit comments

Comments
 (0)