Skip to content

Commit 2facbc8

Browse files
pirxthepilotpgier
authored andcommitted
Parse fdinfo and inotify stats from /proc/[pid]/fdinfo/[fd] (#115)
Signed-off-by: Joon Guillen <[email protected]>
1 parent 3f98efb commit 2facbc8

File tree

5 files changed

+255
-0
lines changed

5 files changed

+255
-0
lines changed

fixtures.ttar

+42
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,48 @@ SymlinkTo: ../../symlinktargets/ghi
4747
Path: fixtures/proc/26231/fd/3
4848
SymlinkTo: ../../symlinktargets/uvw
4949
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
50+
Directory: fixtures/proc/26231/fdinfo
51+
Mode: 755
52+
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
53+
Path: fixtures/proc/26231/fdinfo/0
54+
Lines: 6
55+
pos: 0
56+
flags: 02004000
57+
mnt_id: 13
58+
inotify wd:3 ino:1 sdev:34 mask:fce ignored_mask:0 fhandle-bytes:c fhandle-type:81 f_handle:000000000100000000000000
59+
inotify wd:2 ino:1300016 sdev:fd00002 mask:fce ignored_mask:0 fhandle-bytes:8 fhandle-type:1 f_handle:16003001ed3f022a
60+
inotify wd:1 ino:2e0001 sdev:fd00000 mask:fce ignored_mask:0 fhandle-bytes:8 fhandle-type:1 f_handle:01002e00138e7c65
61+
Mode: 644
62+
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
63+
Path: fixtures/proc/26231/fdinfo/1
64+
Lines: 4
65+
pos: 0
66+
flags: 02004002
67+
mnt_id: 13
68+
eventfd-count: 0
69+
Mode: 644
70+
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
71+
Path: fixtures/proc/26231/fdinfo/10
72+
Lines: 3
73+
pos: 0
74+
flags: 02004002
75+
mnt_id: 9
76+
Mode: 400
77+
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
78+
Path: fixtures/proc/26231/fdinfo/2
79+
Lines: 3
80+
pos: 0
81+
flags: 02004002
82+
mnt_id: 9
83+
Mode: 400
84+
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
85+
Path: fixtures/proc/26231/fdinfo/3
86+
Lines: 3
87+
pos: 0
88+
flags: 02004002
89+
mnt_id: 9
90+
Mode: 400
91+
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
5092
Path: fixtures/proc/26231/io
5193
Lines: 7
5294
rchar: 750339

proc.go

+21
Original file line numberDiff line numberDiff line change
@@ -279,3 +279,24 @@ func (p Proc) fileDescriptors() ([]string, error) {
279279
func (p Proc) path(pa ...string) string {
280280
return p.fs.Path(append([]string{strconv.Itoa(p.PID)}, pa...)...)
281281
}
282+
283+
// FileDescriptorsInfo retrieves information about all file descriptors of
284+
// the process.
285+
func (p Proc) FileDescriptorsInfo() (ProcFDInfos, error) {
286+
names, err := p.fileDescriptors()
287+
if err != nil {
288+
return nil, err
289+
}
290+
291+
var fdinfos ProcFDInfos
292+
293+
for _, n := range names {
294+
fdinfo, err := p.FDInfo(n)
295+
if err != nil {
296+
continue
297+
}
298+
fdinfos = append(fdinfos, *fdinfo)
299+
}
300+
301+
return fdinfos, nil
302+
}

proc_fdinfo.go

+132
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
// Copyright 2019 The Prometheus Authors
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
package procfs
15+
16+
import (
17+
"bufio"
18+
"fmt"
19+
"io/ioutil"
20+
"os"
21+
"regexp"
22+
"strings"
23+
)
24+
25+
// Regexp variables
26+
var (
27+
rPos = regexp.MustCompile(`^pos:\s+(\d+)$`)
28+
rFlags = regexp.MustCompile(`^flags:\s+(\d+)$`)
29+
rMntID = regexp.MustCompile(`^mnt_id:\s+(\d+)$`)
30+
rInotify = regexp.MustCompile(`^inotify`)
31+
)
32+
33+
// ProcFDInfo contains represents file descriptor information.
34+
type ProcFDInfo struct {
35+
// File descriptor
36+
FD string
37+
// File offset
38+
Pos string
39+
// File access mode and status flags
40+
Flags string
41+
// Mount point ID
42+
MntID string
43+
// List of inotify lines (structed) in the fdinfo file (kernel 3.8+ only)
44+
InotifyInfos []InotifyInfo
45+
}
46+
47+
// FDInfo constructor. On kernels older than 3.8, InotifyInfos will always be empty.
48+
func (p Proc) FDInfo(fd string) (*ProcFDInfo, error) {
49+
f, err := os.Open(p.path("fdinfo", fd))
50+
if err != nil {
51+
return nil, err
52+
}
53+
defer f.Close()
54+
55+
fdinfo, err := ioutil.ReadAll(f)
56+
if err != nil {
57+
return nil, fmt.Errorf("could not read %s: %s", f.Name(), err)
58+
}
59+
60+
var text, pos, flags, mntid string
61+
var inotify []InotifyInfo
62+
63+
scanner := bufio.NewScanner(strings.NewReader(string(fdinfo)))
64+
for scanner.Scan() {
65+
text = scanner.Text()
66+
if rPos.MatchString(text) {
67+
pos = rPos.FindStringSubmatch(text)[1]
68+
} else if rFlags.MatchString(text) {
69+
flags = rFlags.FindStringSubmatch(text)[1]
70+
} else if rMntID.MatchString(text) {
71+
mntid = rMntID.FindStringSubmatch(text)[1]
72+
} else if rInotify.MatchString(text) {
73+
newInotify, err := parseInotifyInfo(text)
74+
if err != nil {
75+
return nil, err
76+
}
77+
inotify = append(inotify, *newInotify)
78+
}
79+
}
80+
81+
i := &ProcFDInfo{
82+
FD: fd,
83+
Pos: pos,
84+
Flags: flags,
85+
MntID: mntid,
86+
InotifyInfos: inotify,
87+
}
88+
89+
return i, nil
90+
}
91+
92+
// InotifyInfo represents a single inotify line in the fdinfo file.
93+
type InotifyInfo struct {
94+
// Watch descriptor number
95+
WD string
96+
// Inode number
97+
Ino string
98+
// Device ID
99+
Sdev string
100+
// Mask of events being monitored
101+
Mask string
102+
}
103+
104+
// InotifyInfo constructor. Only available on kernel 3.8+.
105+
func parseInotifyInfo(line string) (*InotifyInfo, error) {
106+
r := regexp.MustCompile(`^inotify\s+wd:([0-9a-f]+)\s+ino:([0-9a-f]+)\s+sdev:([0-9a-f]+)\s+mask:([0-9a-f]+)`)
107+
m := r.FindStringSubmatch(line)
108+
i := &InotifyInfo{
109+
WD: m[1],
110+
Ino: m[2],
111+
Sdev: m[3],
112+
Mask: m[4],
113+
}
114+
return i, nil
115+
}
116+
117+
// ProcFDInfos represents a list of ProcFDInfo structs.
118+
type ProcFDInfos []ProcFDInfo
119+
120+
func (p ProcFDInfos) Len() int { return len(p) }
121+
func (p ProcFDInfos) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
122+
func (p ProcFDInfos) Less(i, j int) bool { return p[i].FD < p[j].FD }
123+
124+
// InotifyWatchLen returns the total number of inotify watches
125+
func (p ProcFDInfos) InotifyWatchLen() (int, error) {
126+
length := 0
127+
for _, f := range p {
128+
length += len(f.InotifyInfos)
129+
}
130+
131+
return length, nil
132+
}

proc_fdinfo_test.go

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright 2019 The Prometheus Authors
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
package procfs
15+
16+
import "testing"
17+
18+
func TestInotifyWatchLen(t *testing.T) {
19+
p1, err := getProcFixtures(t).Proc(26231)
20+
if err != nil {
21+
t.Fatal(err)
22+
}
23+
fdinfos, err := p1.FileDescriptorsInfo()
24+
if err != nil {
25+
t.Fatal(err)
26+
}
27+
l, err := fdinfos.InotifyWatchLen()
28+
if err != nil {
29+
t.Fatal(err)
30+
}
31+
if want, have := 3, l; want != have {
32+
t.Errorf("want length %d, have %d", want, have)
33+
}
34+
}

proc_test.go

+26
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,32 @@ func TestFileDescriptorsLen(t *testing.T) {
223223
}
224224
}
225225

226+
func TestFileDescriptorsInfo(t *testing.T) {
227+
p1, err := getProcFixtures(t).Proc(26231)
228+
if err != nil {
229+
t.Fatal(err)
230+
}
231+
fdinfos, err := p1.FileDescriptorsInfo()
232+
if err != nil {
233+
t.Fatal(err)
234+
}
235+
sort.Sort(fdinfos)
236+
var want = ProcFDInfos{
237+
ProcFDInfo{FD: "0", Pos: "0", Flags: "02004000", MntID: "13", InotifyInfos: []InotifyInfo{
238+
InotifyInfo{WD: "3", Ino: "1", Sdev: "34", Mask: "fce"},
239+
InotifyInfo{WD: "2", Ino: "1300016", Sdev: "fd00002", Mask: "fce"},
240+
InotifyInfo{WD: "1", Ino: "2e0001", Sdev: "fd00000", Mask: "fce"},
241+
}},
242+
ProcFDInfo{FD: "1", Pos: "0", Flags: "02004002", MntID: "13", InotifyInfos: nil},
243+
ProcFDInfo{FD: "10", Pos: "0", Flags: "02004002", MntID: "9", InotifyInfos: nil},
244+
ProcFDInfo{FD: "2", Pos: "0", Flags: "02004002", MntID: "9", InotifyInfos: nil},
245+
ProcFDInfo{FD: "3", Pos: "0", Flags: "02004002", MntID: "9", InotifyInfos: nil},
246+
}
247+
if !reflect.DeepEqual(want, fdinfos) {
248+
t.Errorf("want fdinfos %+v, have %+v", want, fdinfos)
249+
}
250+
}
251+
226252
type byUintptr []uintptr
227253

228254
func (a byUintptr) Len() int { return len(a) }

0 commit comments

Comments
 (0)