Refactor into smaller and more self contained packages
Remove the dependencies from procfs and sysfs on xfs, nfs, bcache, and
disk iostats. And make these package self contained so they
do not bring in any additional dependencies.
Also refactor the iostats package into a blockdevice package, and
improve naming and organization of this package.
Signed-off-by: Paul Gier <pgier@redhat.com>
Paul Gier
5 years ago
23 | 23 | "strings" |
24 | 24 | ) |
25 | 25 | |
26 | // ReadStats retrieves bcache runtime statistics for each bcache. | |
27 | func ReadStats(sysfs string) ([]*Stats, error) { | |
28 | matches, err := filepath.Glob(path.Join(sysfs, "fs/bcache/*-*")) | |
29 | if err != nil { | |
30 | return nil, err | |
31 | } | |
32 | ||
33 | stats := make([]*Stats, 0, len(matches)) | |
34 | for _, uuidPath := range matches { | |
35 | // "*-*" in glob above indicates the name of the bcache. | |
36 | name := filepath.Base(uuidPath) | |
37 | ||
38 | // stats | |
39 | s, err := GetStats(uuidPath) | |
40 | if err != nil { | |
41 | return nil, err | |
42 | } | |
43 | ||
44 | s.Name = name | |
45 | stats = append(stats, s) | |
46 | } | |
47 | ||
48 | return stats, nil | |
49 | } | |
50 | ||
26 | 51 | // ParsePseudoFloat parses the peculiar format produced by bcache's bch_hprint. |
27 | 52 | func parsePseudoFloat(str string) (float64, error) { |
28 | 53 | ss := strings.Split(str, ".") |
16 | 16 | "math" |
17 | 17 | "testing" |
18 | 18 | ) |
19 | ||
20 | func TestFSBcacheStats(t *testing.T) { | |
21 | stats, err := ReadStats("../fixtures/sys") | |
22 | if err != nil { | |
23 | t.Fatalf("failed to parse bcache stats: %v", err) | |
24 | } | |
25 | ||
26 | tests := []struct { | |
27 | name string | |
28 | bdevs int | |
29 | caches int | |
30 | }{ | |
31 | { | |
32 | name: "deaddd54-c735-46d5-868e-f331c5fd7c74", | |
33 | bdevs: 1, | |
34 | caches: 1, | |
35 | }, | |
36 | } | |
37 | ||
38 | const expect = 1 | |
39 | ||
40 | if l := len(stats); l != expect { | |
41 | t.Fatalf("unexpected number of bcache stats: %d", l) | |
42 | } | |
43 | if l := len(tests); l != expect { | |
44 | t.Fatalf("unexpected number of tests: %d", l) | |
45 | } | |
46 | ||
47 | for i, tt := range tests { | |
48 | if want, got := tt.name, stats[i].Name; want != got { | |
49 | t.Errorf("unexpected stats name:\nwant: %q\nhave: %q", want, got) | |
50 | } | |
51 | ||
52 | if want, got := tt.bdevs, len(stats[i].Bdevs); want != got { | |
53 | t.Errorf("unexpected value allocated:\nwant: %d\nhave: %d", want, got) | |
54 | } | |
55 | ||
56 | if want, got := tt.caches, len(stats[i].Caches); want != got { | |
57 | t.Errorf("unexpected value allocated:\nwant: %d\nhave: %d", want, got) | |
58 | } | |
59 | } | |
60 | } | |
19 | 61 | |
20 | 62 | func TestDehumanizeTests(t *testing.T) { |
21 | 63 | dehumanizeTests := []struct { |
0 | // Copyright 2018 The Prometheus Authors | |
1 | // Licensed under the Apache License, Version 2.0 (the "License"); | |
2 | // you may not use this file except in compliance with the License. | |
3 | // You may obtain a copy of the License at | |
4 | // | |
5 | // http://www.apache.org/licenses/LICENSE-2.0 | |
6 | // | |
7 | // Unless required by applicable law or agreed to in writing, software | |
8 | // distributed under the License is distributed on an "AS IS" BASIS, | |
9 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
10 | // See the License for the specific language governing permissions and | |
11 | // limitations under the License. | |
12 | ||
13 | package blockdevice | |
14 | ||
15 | import ( | |
16 | "bufio" | |
17 | "fmt" | |
18 | "io" | |
19 | "io/ioutil" | |
20 | "os" | |
21 | "path" | |
22 | "strings" | |
23 | ) | |
24 | ||
25 | // Info contains identifying information for a block device such as a disk drive | |
26 | type Info struct { | |
27 | MajorNumber uint32 | |
28 | MinorNumber uint32 | |
29 | DeviceName string | |
30 | } | |
31 | ||
32 | // IoStats models the iostats data described in the kernel documentation | |
33 | // https://www.kernel.org/doc/Documentation/iostats.txt, | |
34 | // https://www.kernel.org/doc/Documentation/block/stat.txt, | |
35 | // and https://www.kernel.org/doc/Documentation/ABI/testing/procfs-diskstats | |
36 | // The 4 discard fields were added in Linux kernel 4.18 and will not be available | |
37 | // on all systems. If these fields are not read, they should be set to MaxUint64 | |
38 | type IoStats struct { | |
39 | // ReadIOs is the number of reads completed successfully. | |
40 | ReadIOs uint64 | |
41 | // ReadMerges is the number of reads merged. Reads and writes | |
42 | // which are adjacent to each other may be merged for efficiency. | |
43 | ReadMerges uint64 | |
44 | // ReadSectors is the total number of sectors read successfully. | |
45 | ReadSectors uint64 | |
46 | // ReadTicks is the total number of milliseconds spent by all reads. | |
47 | ReadTicks uint64 | |
48 | // WriteIOs is the total number of writes completed successfully. | |
49 | WriteIOs uint64 | |
50 | // WriteMerges is the number of reads merged. | |
51 | WriteMerges uint64 | |
52 | // WriteSectors is the total number of sectors written successfully. | |
53 | WriteSectors uint64 | |
54 | // WriteTicks is the total number of milliseconds spent by all writes. | |
55 | WriteTicks uint64 | |
56 | // IOsInProgress is number of I/Os currently in progress. | |
57 | IOsInProgress uint64 | |
58 | // IOsTotalTicks is the number of milliseconds spent doing I/Os. | |
59 | // This field increases so long as IosInProgress is nonzero. | |
60 | IOsTotalTicks uint64 | |
61 | // WeightedIOTicks is the weighted number of milliseconds spent doing I/Os. | |
62 | // This can also be used to estimate average queue wait time for requests. | |
63 | WeightedIOTicks uint64 | |
64 | // DiscardIOs is the total number of discards completed successfully. | |
65 | DiscardIOs uint64 | |
66 | // DiscardMerges is the number of discards merged. | |
67 | DiscardMerges uint64 | |
68 | // DiscardSectors is the total number of sectors discarded successfully. | |
69 | DiscardSectors uint64 | |
70 | // DiscardTicks is the total number of milliseconds spent by all discards. | |
71 | DiscardTicks uint64 | |
72 | } | |
73 | ||
74 | // Diskstats combines the device Info and IOStats | |
75 | type Diskstats struct { | |
76 | Info | |
77 | IoStats | |
78 | } | |
79 | ||
80 | const ( | |
81 | procDiskstatsPath = "diskstats" | |
82 | procDiskstatsFormat = "%d %d %s %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d" | |
83 | sysBlockPath = "block" | |
84 | sysBlockStatFormat = "%d %d %d %d %d %d %d %d %d %d %d %d %d %d %d" | |
85 | ) | |
86 | ||
87 | // ReadProcDiskstats reads the diskstats file and returns | |
88 | // an array of Diskstats (one per line/device) | |
89 | func ReadProcDiskstats(procfs string) ([]Diskstats, error) { | |
90 | file, err := os.Open(path.Join(procfs, procDiskstatsPath)) | |
91 | if err != nil { | |
92 | return nil, err | |
93 | } | |
94 | defer file.Close() | |
95 | ||
96 | diskstats := []Diskstats{} | |
97 | scanner := bufio.NewScanner(file) | |
98 | for scanner.Scan() { | |
99 | d := &Diskstats{} | |
100 | count, err := fmt.Sscanf(scanner.Text(), procDiskstatsFormat, | |
101 | &d.MajorNumber, | |
102 | &d.MinorNumber, | |
103 | &d.DeviceName, | |
104 | &d.ReadIOs, | |
105 | &d.ReadMerges, | |
106 | &d.ReadSectors, | |
107 | &d.ReadTicks, | |
108 | &d.WriteIOs, | |
109 | &d.WriteMerges, | |
110 | &d.WriteSectors, | |
111 | &d.WriteTicks, | |
112 | &d.IOsInProgress, | |
113 | &d.IOsTotalTicks, | |
114 | &d.WeightedIOTicks, | |
115 | &d.DiscardIOs, | |
116 | &d.DiscardMerges, | |
117 | &d.DiscardSectors, | |
118 | &d.DiscardTicks) | |
119 | if err != nil && err != io.EOF { | |
120 | return diskstats, err | |
121 | } | |
122 | if count == 14 || count == 18 { | |
123 | diskstats = append(diskstats, *d) | |
124 | } | |
125 | } | |
126 | return diskstats, nil | |
127 | } | |
128 | ||
129 | // ListSysBlockDevices lists the device names from /sys/block/<dev> | |
130 | func ListSysBlockDevices(sysfs string) ([]string, error) { | |
131 | fmt.Println(path.Join(sysfs, sysBlockPath)) | |
132 | deviceDirs, err := ioutil.ReadDir(path.Join(sysfs, sysBlockPath)) | |
133 | if err != nil { | |
134 | return nil, err | |
135 | } | |
136 | devices := []string{} | |
137 | for _, deviceDir := range deviceDirs { | |
138 | if deviceDir.IsDir() { | |
139 | devices = append(devices, deviceDir.Name()) | |
140 | } | |
141 | } | |
142 | return devices, nil | |
143 | } | |
144 | ||
145 | // ReadBlockDeviceStat returns stats for the block device read from /sys/block/<device>/stat. | |
146 | func ReadBlockDeviceStat(sysfs string, device string) (IoStats, error) { | |
147 | stat := IoStats{} | |
148 | bytes, err := ioutil.ReadFile(path.Join(sysfs, sysBlockPath, device, "stat")) | |
149 | if err != nil { | |
150 | return stat, err | |
151 | } | |
152 | count, err := fmt.Sscanf(strings.TrimSpace(string(bytes)), sysBlockStatFormat, | |
153 | &stat.ReadIOs, | |
154 | &stat.ReadMerges, | |
155 | &stat.ReadSectors, | |
156 | &stat.ReadTicks, | |
157 | &stat.WriteIOs, | |
158 | &stat.WriteMerges, | |
159 | &stat.WriteSectors, | |
160 | &stat.WriteTicks, | |
161 | &stat.IOsInProgress, | |
162 | &stat.IOsTotalTicks, | |
163 | &stat.WeightedIOTicks, | |
164 | &stat.DiscardIOs, | |
165 | &stat.DiscardMerges, | |
166 | &stat.DiscardSectors, | |
167 | &stat.DiscardTicks, | |
168 | ) | |
169 | if count == 11 || count == 15 { | |
170 | return stat, nil | |
171 | } | |
172 | return IoStats{}, err | |
173 | } |
0 | // Copyright 2018 The Prometheus Authors | |
1 | // Licensed under the Apache License, Version 2.0 (the "License"); | |
2 | // you may not use this file except in compliance with the License. | |
3 | // You may obtain a copy of the License at | |
4 | // | |
5 | // http://www.apache.org/licenses/LICENSE-2.0 | |
6 | // | |
7 | // Unless required by applicable law or agreed to in writing, software | |
8 | // distributed under the License is distributed on an "AS IS" BASIS, | |
9 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
10 | // See the License for the specific language governing permissions and | |
11 | // limitations under the License. | |
12 | ||
13 | package blockdevice | |
14 | ||
15 | import ( | |
16 | "testing" | |
17 | ) | |
18 | ||
19 | const ( | |
20 | failMsgFormat = "%v, expected %v, actual %v" | |
21 | procfsFixtures = "../fixtures/proc" | |
22 | sysfsFixtures = "../fixtures/sys" | |
23 | ) | |
24 | ||
25 | func TestDiskstats(t *testing.T) { | |
26 | diskstats, err := ReadProcDiskstats(procfsFixtures) | |
27 | if err != nil { | |
28 | t.Fatal(err) | |
29 | } | |
30 | expectedNumOfDevices := 49 | |
31 | if len(diskstats) != expectedNumOfDevices { | |
32 | t.Errorf(failMsgFormat, "Incorrect number of devices", expectedNumOfDevices, len(diskstats)) | |
33 | } | |
34 | if diskstats[0].DeviceName != "ram0" { | |
35 | t.Errorf(failMsgFormat, "Incorrect device name", "ram0", diskstats[0].DeviceName) | |
36 | } | |
37 | if diskstats[24].WriteIOs != 28444756 { | |
38 | t.Errorf(failMsgFormat, "Incorrect writes completed", 28444756, diskstats[24].WriteIOs) | |
39 | } | |
40 | if diskstats[48].DiscardTicks != 11130 { | |
41 | t.Errorf(failMsgFormat, "Incorrect discard time", 11130, diskstats[48].DiscardTicks) | |
42 | } | |
43 | } | |
44 | ||
45 | func TestBlockDevice(t *testing.T) { | |
46 | devices, err := ListSysBlockDevices(sysfsFixtures) | |
47 | if err != nil { | |
48 | t.Fatal(err) | |
49 | } | |
50 | expectedNumOfDevices := 2 | |
51 | if len(devices) != expectedNumOfDevices { | |
52 | t.Fatalf(failMsgFormat, "Incorrect number of devices", expectedNumOfDevices, len(devices)) | |
53 | } | |
54 | if devices[0] != "dm-0" { | |
55 | t.Errorf(failMsgFormat, "Incorrect device name", "dm-0", devices[0]) | |
56 | } | |
57 | device0stats, err := ReadBlockDeviceStat(sysfsFixtures, devices[0]) | |
58 | if err != nil { | |
59 | t.Fatal(err) | |
60 | } | |
61 | if device0stats.ReadIOs != 6447303 { | |
62 | t.Errorf(failMsgFormat, "Incorrect read I/Os", 6447303, device0stats.ReadIOs) | |
63 | } | |
64 | if device0stats.WeightedIOTicks != 6088971 { | |
65 | t.Errorf(failMsgFormat, "Incorrect time in queue", 6088971, device0stats.WeightedIOTicks) | |
66 | } | |
67 | device1stats, err := ReadBlockDeviceStat(sysfsFixtures, devices[1]) | |
68 | if err != nil { | |
69 | t.Fatal(err) | |
70 | } | |
71 | if device1stats.WriteSectors != 286915323 { | |
72 | t.Errorf(failMsgFormat, "Incorrect write merges", 286915323, device1stats.WriteSectors) | |
73 | } | |
74 | if device1stats.DiscardTicks != 12 { | |
75 | t.Errorf(failMsgFormat, "Incorrect discard ticks", 12, device1stats.DiscardTicks) | |
76 | } | |
77 | } |
0 | // Copyright 2018 The Prometheus Authors | |
1 | // Licensed under the Apache License, Version 2.0 (the "License"); | |
2 | // you may not use this file except in compliance with the License. | |
3 | // You may obtain a copy of the License at | |
4 | // | |
5 | // http://www.apache.org/licenses/LICENSE-2.0 | |
6 | // | |
7 | // Unless required by applicable law or agreed to in writing, software | |
8 | // distributed under the License is distributed on an "AS IS" BASIS, | |
9 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
10 | // See the License for the specific language governing permissions and | |
11 | // limitations under the License. | |
12 | ||
13 | package procfs | |
14 | ||
15 | import ( | |
16 | "bufio" | |
17 | "fmt" | |
18 | "io" | |
19 | "os" | |
20 | ||
21 | "github.com/prometheus/procfs/iostats" | |
22 | ) | |
23 | ||
24 | const ( | |
25 | diskstatsFilename = "diskstats" | |
26 | statFormat = "%d %d %s %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d" | |
27 | ) | |
28 | ||
29 | // NewDiskstats reads the diskstats file and returns | |
30 | // an array of Diskstats (one per line/device) | |
31 | func NewDiskstats() ([]iostats.IODeviceStats, error) { | |
32 | fs, err := NewFS(DefaultMountPoint) | |
33 | if err != nil { | |
34 | return nil, err | |
35 | } | |
36 | ||
37 | return fs.NewDiskstats() | |
38 | } | |
39 | ||
40 | // NewDiskstats reads the diskstats file and returns | |
41 | // an array of Diskstats (one per line/device) | |
42 | func (fs FS) NewDiskstats() ([]iostats.IODeviceStats, error) { | |
43 | file, err := os.Open(fs.Path(diskstatsFilename)) | |
44 | if err != nil { | |
45 | return nil, err | |
46 | } | |
47 | defer file.Close() | |
48 | ||
49 | diskstats := []iostats.IODeviceStats{} | |
50 | scanner := bufio.NewScanner(file) | |
51 | for scanner.Scan() { | |
52 | d := &iostats.IODeviceStats{} | |
53 | count, err := fmt.Sscanf(scanner.Text(), statFormat, | |
54 | &d.MajorNumber, | |
55 | &d.MinorNumber, | |
56 | &d.DeviceName, | |
57 | &d.ReadIOs, | |
58 | &d.ReadMerges, | |
59 | &d.ReadSectors, | |
60 | &d.ReadTicks, | |
61 | &d.WriteIOs, | |
62 | &d.WriteMerges, | |
63 | &d.WriteSectors, | |
64 | &d.WriteTicks, | |
65 | &d.IOsInProgress, | |
66 | &d.IOsTotalTicks, | |
67 | &d.WeightedIOTicks, | |
68 | &d.DiscardIOs, | |
69 | &d.DiscardMerges, | |
70 | &d.DiscardSectors, | |
71 | &d.DiscardTicks) | |
72 | if err != nil && err != io.EOF { | |
73 | return diskstats, err | |
74 | } | |
75 | if count == 14 || count == 18 { | |
76 | diskstats = append(diskstats, *d) | |
77 | } | |
78 | } | |
79 | return diskstats, nil | |
80 | } |
0 | // Copyright 2018 The Prometheus Authors | |
1 | // Licensed under the Apache License, Version 2.0 (the "License"); | |
2 | // you may not use this file except in compliance with the License. | |
3 | // You may obtain a copy of the License at | |
4 | // | |
5 | // http://www.apache.org/licenses/LICENSE-2.0 | |
6 | // | |
7 | // Unless required by applicable law or agreed to in writing, software | |
8 | // distributed under the License is distributed on an "AS IS" BASIS, | |
9 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
10 | // See the License for the specific language governing permissions and | |
11 | // limitations under the License. | |
12 | ||
13 | package procfs | |
14 | ||
15 | import ( | |
16 | "testing" | |
17 | ) | |
18 | ||
19 | const ( | |
20 | failMsgFormat = "%v, expected %v, actual %v" | |
21 | expectedNumOfDevices = 49 | |
22 | ) | |
23 | ||
24 | func TestDiskstats(t *testing.T) { | |
25 | diskstats, err := FS(procTestFixtures).NewDiskstats() | |
26 | if err != nil { | |
27 | t.Fatal(err) | |
28 | } | |
29 | if len(diskstats) != expectedNumOfDevices { | |
30 | t.Errorf(failMsgFormat, "Incorrect number of devices", expectedNumOfDevices, len(diskstats)) | |
31 | } | |
32 | if diskstats[0].DeviceName != "ram0" { | |
33 | t.Errorf(failMsgFormat, "Incorrect device name", "ram0", diskstats[0].DeviceName) | |
34 | } | |
35 | if diskstats[24].WriteIOs != 28444756 { | |
36 | t.Errorf(failMsgFormat, "Incorrect writes completed", 28444756, diskstats[24].WriteIOs) | |
37 | } | |
38 | if diskstats[48].DiscardTicks != 11130 { | |
39 | t.Errorf(failMsgFormat, "Incorrect discard time", 11130, diskstats[48].DiscardTicks) | |
40 | } | |
41 | } |
16 | 16 | "fmt" |
17 | 17 | "os" |
18 | 18 | "path" |
19 | ||
20 | "github.com/prometheus/procfs/nfs" | |
21 | "github.com/prometheus/procfs/xfs" | |
22 | 19 | ) |
23 | 20 | |
24 | 21 | // FS represents the pseudo-filesystem proc, which provides an interface to |
46 | 43 | func (fs FS) Path(p ...string) string { |
47 | 44 | return path.Join(append([]string{string(fs)}, p...)...) |
48 | 45 | } |
49 | ||
50 | // XFSStats retrieves XFS filesystem runtime statistics. | |
51 | func (fs FS) XFSStats() (*xfs.Stats, error) { | |
52 | f, err := os.Open(fs.Path("fs/xfs/stat")) | |
53 | if err != nil { | |
54 | return nil, err | |
55 | } | |
56 | defer f.Close() | |
57 | ||
58 | return xfs.ParseStats(f) | |
59 | } | |
60 | ||
61 | // NFSClientRPCStats retrieves NFS client RPC statistics. | |
62 | func (fs FS) NFSClientRPCStats() (*nfs.ClientRPCStats, error) { | |
63 | f, err := os.Open(fs.Path("net/rpc/nfs")) | |
64 | if err != nil { | |
65 | return nil, err | |
66 | } | |
67 | defer f.Close() | |
68 | ||
69 | return nfs.ParseClientRPCStats(f) | |
70 | } | |
71 | ||
72 | // NFSdServerRPCStats retrieves NFS daemon RPC statistics. | |
73 | func (fs FS) NFSdServerRPCStats() (*nfs.ServerRPCStats, error) { | |
74 | f, err := os.Open(fs.Path("net/rpc/nfsd")) | |
75 | if err != nil { | |
76 | return nil, err | |
77 | } | |
78 | defer f.Close() | |
79 | ||
80 | return nfs.ParseServerRPCStats(f) | |
81 | } |
27 | 27 | t.Error("want NewFS to fail if mount point is not a directory") |
28 | 28 | } |
29 | 29 | } |
30 | ||
31 | func TestFSXFSStats(t *testing.T) { | |
32 | stats, err := FS(procTestFixtures).XFSStats() | |
33 | if err != nil { | |
34 | t.Fatalf("failed to parse XFS stats: %v", err) | |
35 | } | |
36 | ||
37 | // Very lightweight test just to sanity check the path used | |
38 | // to open XFS stats. Heavier tests in package xfs. | |
39 | if want, got := uint32(92447), stats.ExtentAllocation.ExtentsAllocated; want != got { | |
40 | t.Errorf("unexpected extents allocated:\nwant: %d\nhave: %d", want, got) | |
41 | } | |
42 | } |
0 | // Copyright 2018 The Prometheus Authors | |
1 | // Licensed under the Apache License, Version 2.0 (the "License"); | |
2 | // you may not use this file except in compliance with the License. | |
3 | // You may obtain a copy of the License at | |
4 | // | |
5 | // http://www.apache.org/licenses/LICENSE-2.0 | |
6 | // | |
7 | // Unless required by applicable law or agreed to in writing, software | |
8 | // distributed under the License is distributed on an "AS IS" BASIS, | |
9 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
10 | // See the License for the specific language governing permissions and | |
11 | // limitations under the License. | |
12 | ||
13 | package iostats | |
14 | ||
15 | // IODevice contains identifying information for an I/O device | |
16 | type IODevice struct { | |
17 | MajorNumber uint32 | |
18 | MinorNumber uint32 | |
19 | DeviceName string | |
20 | } | |
21 | ||
22 | // IOStats models the iostats data described in the kernel documentation | |
23 | // https://www.kernel.org/doc/Documentation/iostats.txt, | |
24 | // https://www.kernel.org/doc/Documentation/block/stat.txt, | |
25 | // and https://www.kernel.org/doc/Documentation/ABI/testing/procfs-diskstats | |
26 | type IOStats struct { | |
27 | // ReadIOs is the number of reads completed successfully. | |
28 | ReadIOs uint64 | |
29 | // ReadMerges is the number of reads merged. Reads and writes | |
30 | // which are adjacent to each other may be merged for efficiency. | |
31 | ReadMerges uint64 | |
32 | // ReadSectors is the total number of sectors read successfully. | |
33 | ReadSectors uint64 | |
34 | // ReadTicks is the total number of milliseconds spent by all reads. | |
35 | ReadTicks uint64 | |
36 | // WriteIOs is the total number of writes completed successfully. | |
37 | WriteIOs uint64 | |
38 | // WriteMerges is the number of reads merged. | |
39 | WriteMerges uint64 | |
40 | // WriteSectors is the total number of sectors written successfully. | |
41 | WriteSectors uint64 | |
42 | // WriteTicks is the total number of milliseconds spent by all writes. | |
43 | WriteTicks uint64 | |
44 | // IOsInProgress is number of I/Os currently in progress. | |
45 | IOsInProgress uint64 | |
46 | // IOsTotalTicks is the number of milliseconds spent doing I/Os. | |
47 | // This field increases so long as IosInProgress is nonzero. | |
48 | IOsTotalTicks uint64 | |
49 | // WeightedIOTicks is the weighted number of milliseconds spent doing I/Os. | |
50 | // This can also be used to estimate average queue wait time for requests. | |
51 | WeightedIOTicks uint64 | |
52 | // DiscardIOs is the total number of discards completed successfully. | |
53 | DiscardIOs uint64 | |
54 | // DiscardMerges is the number of discards merged. | |
55 | DiscardMerges uint64 | |
56 | // DiscardSectors is the total number of sectors discarded successfully. | |
57 | DiscardSectors uint64 | |
58 | // DiscardTicks is the total number of milliseconds spent by all discards. | |
59 | DiscardTicks uint64 | |
60 | } | |
61 | ||
62 | // IODeviceStats combines IODevice and IOStats | |
63 | type IODeviceStats struct { | |
64 | IODevice | |
65 | IOStats | |
66 | } |
13 | 13 | // Package nfs implements parsing of /proc/net/rpc/nfsd. |
14 | 14 | // Fields are documented in https://www.svennd.be/nfsd-stats-explained-procnetrpcnfsd/ |
15 | 15 | package nfs |
16 | ||
17 | import ( | |
18 | "os" | |
19 | "path" | |
20 | ) | |
16 | 21 | |
17 | 22 | // ReplyCache models the "rc" line. |
18 | 23 | type ReplyCache struct { |
260 | 265 | ServerV4Stats ServerV4Stats |
261 | 266 | V4Ops V4Ops |
262 | 267 | } |
268 | ||
269 | // ReadClientRPCStats retrieves NFS client RPC statistics | |
270 | // from proc/net/rpc/nfs. | |
271 | func ReadClientRPCStats(procfs string) (*ClientRPCStats, error) { | |
272 | f, err := os.Open(path.Join(procfs, "net/rpc/nfs")) | |
273 | if err != nil { | |
274 | return nil, err | |
275 | } | |
276 | defer f.Close() | |
277 | ||
278 | return ParseClientRPCStats(f) | |
279 | } | |
280 | ||
281 | // ReadServerRPCStats retrieves NFS daemon RPC statistics | |
282 | // from proc/net/rpc/nfsd. | |
283 | func ReadServerRPCStats(procfs string) (*ServerRPCStats, error) { | |
284 | f, err := os.Open(path.Join(procfs, "net/rpc/nfsd")) | |
285 | if err != nil { | |
286 | return nil, err | |
287 | } | |
288 | defer f.Close() | |
289 | ||
290 | return ParseServerRPCStats(f) | |
291 | } |
0 | // Copyright 2018 The Prometheus Authors | |
1 | // Licensed under the Apache License, Version 2.0 (the "License"); | |
2 | // you may not use this file except in compliance with the License. | |
3 | // You may obtain a copy of the License at | |
4 | // | |
5 | // http://www.apache.org/licenses/LICENSE-2.0 | |
6 | // | |
7 | // Unless required by applicable law or agreed to in writing, software | |
8 | // distributed under the License is distributed on an "AS IS" BASIS, | |
9 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
10 | // See the License for the specific language governing permissions and | |
11 | // limitations under the License. | |
12 | ||
13 | // +build !windows | |
14 | ||
15 | package sysfs | |
16 | ||
17 | import ( | |
18 | "fmt" | |
19 | "io/ioutil" | |
20 | "strings" | |
21 | ||
22 | "github.com/prometheus/procfs/iostats" | |
23 | ) | |
24 | ||
25 | const ( | |
26 | blockPath = "block" | |
27 | blockDeviceStatFormat = "%d %d %d %d %d %d %d %d %d %d %d %d %d %d %d" | |
28 | ) | |
29 | ||
30 | // BlockDevice represents block device in the sys filesystem | |
31 | // Docs here: https://www.kernel.org/doc/Documentation/block/ | |
32 | type BlockDevice struct { | |
33 | DeviceName string | |
34 | fs FS | |
35 | } | |
36 | ||
37 | // AllBlockDevices gets the list of block devices from the sys file system | |
38 | func (fs FS) AllBlockDevices() ([]BlockDevice, error) { | |
39 | deviceDirs, err := ioutil.ReadDir(fs.Path(blockPath)) | |
40 | if err != nil { | |
41 | return nil, err | |
42 | } | |
43 | devices := []BlockDevice{} | |
44 | for _, deviceDir := range deviceDirs { | |
45 | if deviceDir.IsDir() { | |
46 | devices = append(devices, BlockDevice{deviceDir.Name(), fs}) | |
47 | } | |
48 | } | |
49 | return devices, nil | |
50 | } | |
51 | ||
52 | // NewBlockDeviceStat returns stats for the block device read from /sys/block/<device>/stat. | |
53 | func (d BlockDevice) NewBlockDeviceStat() (iostats.IOStats, error) { | |
54 | stat := iostats.IOStats{} | |
55 | bytes, err := ioutil.ReadFile(d.fs.Path(blockPath, d.DeviceName, "stat")) | |
56 | if err != nil { | |
57 | return stat, err | |
58 | } | |
59 | count, err := fmt.Sscanf(strings.TrimSpace(string(bytes)), blockDeviceStatFormat, | |
60 | &stat.ReadIOs, | |
61 | &stat.ReadMerges, | |
62 | &stat.ReadSectors, | |
63 | &stat.ReadTicks, | |
64 | &stat.WriteIOs, | |
65 | &stat.WriteMerges, | |
66 | &stat.WriteSectors, | |
67 | &stat.WriteTicks, | |
68 | &stat.IOsInProgress, | |
69 | &stat.IOsTotalTicks, | |
70 | &stat.WeightedIOTicks, | |
71 | &stat.DiscardIOs, | |
72 | &stat.DiscardMerges, | |
73 | &stat.DiscardSectors, | |
74 | &stat.DiscardTicks, | |
75 | ) | |
76 | if count == 11 || count == 15 { | |
77 | return stat, nil | |
78 | } | |
79 | return iostats.IOStats{}, err | |
80 | } |
0 | // Copyright 2018 The Prometheus Authors | |
1 | // Licensed under the Apache License, Version 2.0 (the "License"); | |
2 | // you may not use this file except in compliance with the License. | |
3 | // You may obtain a copy of the License at | |
4 | // | |
5 | // http://www.apache.org/licenses/LICENSE-2.0 | |
6 | // | |
7 | // Unless required by applicable law or agreed to in writing, software | |
8 | // distributed under the License is distributed on an "AS IS" BASIS, | |
9 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
10 | // See the License for the specific language governing permissions and | |
11 | // limitations under the License. | |
12 | ||
13 | // +build !windows | |
14 | ||
15 | package sysfs | |
16 | ||
17 | import ( | |
18 | "testing" | |
19 | ) | |
20 | ||
21 | const ( | |
22 | failMsgFormat = "%v, expected %v, actual %v" | |
23 | expectedNumOfDevices = 2 | |
24 | ) | |
25 | ||
26 | func TestBlockDevice(t *testing.T) { | |
27 | devices, err := FS(sysTestFixtures).AllBlockDevices() | |
28 | if err != nil { | |
29 | t.Fatal(err) | |
30 | } | |
31 | if len(devices) != expectedNumOfDevices { | |
32 | t.Fatalf(failMsgFormat, "Incorrect number of devices", expectedNumOfDevices, len(devices)) | |
33 | } | |
34 | if devices[0].DeviceName != "dm-0" { | |
35 | t.Errorf(failMsgFormat, "Incorrect device name", "dm-0", devices[0].DeviceName) | |
36 | } | |
37 | device0stats, err := devices[0].NewBlockDeviceStat() | |
38 | if err != nil { | |
39 | t.Fatal(err) | |
40 | } | |
41 | if device0stats.ReadIOs != 6447303 { | |
42 | t.Errorf(failMsgFormat, "Incorrect read I/Os", 6447303, device0stats.ReadIOs) | |
43 | } | |
44 | if device0stats.WeightedIOTicks != 6088971 { | |
45 | t.Errorf(failMsgFormat, "Incorrect time in queue", 6088971, device0stats.WeightedIOTicks) | |
46 | } | |
47 | device1stats, err := devices[1].NewBlockDeviceStat() | |
48 | if err != nil { | |
49 | t.Fatal(err) | |
50 | } | |
51 | if device1stats.WriteSectors != 286915323 { | |
52 | t.Errorf(failMsgFormat, "Incorrect write merges", 286915323, device1stats.WriteSectors) | |
53 | } | |
54 | if device1stats.DiscardTicks != 12 { | |
55 | t.Errorf(failMsgFormat, "Incorrect discard ticks", 12, device1stats.DiscardTicks) | |
56 | } | |
57 | } |
16 | 16 | "fmt" |
17 | 17 | "os" |
18 | 18 | "path/filepath" |
19 | ||
20 | "github.com/prometheus/procfs/bcache" | |
21 | "github.com/prometheus/procfs/xfs" | |
22 | 19 | ) |
23 | 20 | |
24 | 21 | // FS represents the pseudo-filesystem sys, which provides an interface to |
46 | 43 | func (fs FS) Path(p ...string) string { |
47 | 44 | return filepath.Join(append([]string{string(fs)}, p...)...) |
48 | 45 | } |
49 | ||
50 | // XFSStats retrieves XFS filesystem runtime statistics for each mounted XFS | |
51 | // filesystem. Only available on kernel 4.4+. On older kernels, an empty | |
52 | // slice of *xfs.Stats will be returned. | |
53 | func (fs FS) XFSStats() ([]*xfs.Stats, error) { | |
54 | matches, err := filepath.Glob(fs.Path("fs/xfs/*/stats/stats")) | |
55 | if err != nil { | |
56 | return nil, err | |
57 | } | |
58 | ||
59 | stats := make([]*xfs.Stats, 0, len(matches)) | |
60 | for _, m := range matches { | |
61 | f, err := os.Open(m) | |
62 | if err != nil { | |
63 | return nil, err | |
64 | } | |
65 | ||
66 | // "*" used in glob above indicates the name of the filesystem. | |
67 | name := filepath.Base(filepath.Dir(filepath.Dir(m))) | |
68 | ||
69 | // File must be closed after parsing, regardless of success or | |
70 | // failure. Defer is not used because of the loop. | |
71 | s, err := xfs.ParseStats(f) | |
72 | _ = f.Close() | |
73 | if err != nil { | |
74 | return nil, err | |
75 | } | |
76 | ||
77 | s.Name = name | |
78 | stats = append(stats, s) | |
79 | } | |
80 | ||
81 | return stats, nil | |
82 | } | |
83 | ||
84 | // BcacheStats retrieves bcache runtime statistics for each bcache. | |
85 | func (fs FS) BcacheStats() ([]*bcache.Stats, error) { | |
86 | matches, err := filepath.Glob(fs.Path("fs/bcache/*-*")) | |
87 | if err != nil { | |
88 | return nil, err | |
89 | } | |
90 | ||
91 | stats := make([]*bcache.Stats, 0, len(matches)) | |
92 | for _, uuidPath := range matches { | |
93 | // "*-*" in glob above indicates the name of the bcache. | |
94 | name := filepath.Base(uuidPath) | |
95 | ||
96 | // stats | |
97 | s, err := bcache.GetStats(uuidPath) | |
98 | if err != nil { | |
99 | return nil, err | |
100 | } | |
101 | ||
102 | s.Name = name | |
103 | stats = append(stats, s) | |
104 | } | |
105 | ||
106 | return stats, nil | |
107 | } |
27 | 27 | t.Error("want NewFS to fail if mount point is not a directory") |
28 | 28 | } |
29 | 29 | } |
30 | ||
31 | func TestFSXFSStats(t *testing.T) { | |
32 | stats, err := FS(sysTestFixtures).XFSStats() | |
33 | if err != nil { | |
34 | t.Fatalf("failed to parse XFS stats: %v", err) | |
35 | } | |
36 | ||
37 | tests := []struct { | |
38 | name string | |
39 | allocated uint32 | |
40 | }{ | |
41 | { | |
42 | name: "sda1", | |
43 | allocated: 1, | |
44 | }, | |
45 | { | |
46 | name: "sdb1", | |
47 | allocated: 2, | |
48 | }, | |
49 | } | |
50 | ||
51 | const expect = 2 | |
52 | ||
53 | if l := len(stats); l != expect { | |
54 | t.Fatalf("unexpected number of XFS stats: %d", l) | |
55 | } | |
56 | if l := len(tests); l != expect { | |
57 | t.Fatalf("unexpected number of tests: %d", l) | |
58 | } | |
59 | ||
60 | for i, tt := range tests { | |
61 | if want, got := tt.name, stats[i].Name; want != got { | |
62 | t.Errorf("unexpected stats name:\nwant: %q\nhave: %q", want, got) | |
63 | } | |
64 | ||
65 | if want, got := tt.allocated, stats[i].ExtentAllocation.ExtentsAllocated; want != got { | |
66 | t.Errorf("unexpected extents allocated:\nwant: %d\nhave: %d", want, got) | |
67 | } | |
68 | } | |
69 | } | |
70 | ||
71 | func TestFSBcacheStats(t *testing.T) { | |
72 | stats, err := FS(sysTestFixtures).BcacheStats() | |
73 | if err != nil { | |
74 | t.Fatalf("failed to parse bcache stats: %v", err) | |
75 | } | |
76 | ||
77 | tests := []struct { | |
78 | name string | |
79 | bdevs int | |
80 | caches int | |
81 | }{ | |
82 | { | |
83 | name: "deaddd54-c735-46d5-868e-f331c5fd7c74", | |
84 | bdevs: 1, | |
85 | caches: 1, | |
86 | }, | |
87 | } | |
88 | ||
89 | const expect = 1 | |
90 | ||
91 | if l := len(stats); l != expect { | |
92 | t.Fatalf("unexpected number of bcache stats: %d", l) | |
93 | } | |
94 | if l := len(tests); l != expect { | |
95 | t.Fatalf("unexpected number of tests: %d", l) | |
96 | } | |
97 | ||
98 | for i, tt := range tests { | |
99 | if want, got := tt.name, stats[i].Name; want != got { | |
100 | t.Errorf("unexpected stats name:\nwant: %q\nhave: %q", want, got) | |
101 | } | |
102 | ||
103 | if want, got := tt.bdevs, len(stats[i].Bdevs); want != got { | |
104 | t.Errorf("unexpected value allocated:\nwant: %d\nhave: %d", want, got) | |
105 | } | |
106 | ||
107 | if want, got := tt.caches, len(stats[i].Caches); want != got { | |
108 | t.Errorf("unexpected value allocated:\nwant: %d\nhave: %d", want, got) | |
109 | } | |
110 | } | |
111 | } |
17 | 17 | "strings" |
18 | 18 | "testing" |
19 | 19 | |
20 | "github.com/prometheus/procfs" | |
21 | 20 | "github.com/prometheus/procfs/xfs" |
22 | 21 | ) |
23 | 22 | |
424 | 423 | stats, err = xfs.ParseStats(strings.NewReader(tt.s)) |
425 | 424 | } |
426 | 425 | if tt.fs { |
427 | stats, err = procfs.FS("../fixtures/proc").XFSStats() | |
426 | stats, err = xfs.ReadProcStat("../fixtures/proc") | |
428 | 427 | } |
429 | 428 | |
430 | 429 | if tt.invalid && err == nil { |
12 | 12 | |
13 | 13 | // Package xfs provides access to statistics exposed by the XFS filesystem. |
14 | 14 | package xfs |
15 | ||
16 | import ( | |
17 | "os" | |
18 | "path" | |
19 | "path/filepath" | |
20 | ) | |
15 | 21 | |
16 | 22 | // Stats contains XFS filesystem runtime statistics, parsed from |
17 | 23 | // /proc/fs/xfs/stat. |
160 | 166 | WriteBytes uint64 |
161 | 167 | ReadBytes uint64 |
162 | 168 | } |
169 | ||
170 | // ReadProcStat retrieves XFS filesystem runtime statistics | |
171 | // from proc/fs/xfs/stat given the profs mount point. | |
172 | func ReadProcStat(procfs string) (*Stats, error) { | |
173 | f, err := os.Open(path.Join(procfs, "fs/xfs/stat")) | |
174 | if err != nil { | |
175 | return nil, err | |
176 | } | |
177 | defer f.Close() | |
178 | ||
179 | return ParseStats(f) | |
180 | } | |
181 | ||
182 | // ReadSysStats retrieves XFS filesystem runtime statistics for each mounted XFS | |
183 | // filesystem. Only available on kernel 4.4+. On older kernels, an empty | |
184 | // slice of *xfs.Stats will be returned. | |
185 | func ReadSysStats(sysfs string) ([]*Stats, error) { | |
186 | matches, err := filepath.Glob(path.Join(sysfs, "fs/xfs/*/stats/stats")) | |
187 | if err != nil { | |
188 | return nil, err | |
189 | } | |
190 | ||
191 | stats := make([]*Stats, 0, len(matches)) | |
192 | for _, m := range matches { | |
193 | f, err := os.Open(m) | |
194 | if err != nil { | |
195 | return nil, err | |
196 | } | |
197 | ||
198 | // "*" used in glob above indicates the name of the filesystem. | |
199 | name := filepath.Base(filepath.Dir(filepath.Dir(m))) | |
200 | ||
201 | // File must be closed after parsing, regardless of success or | |
202 | // failure. Defer is not used because of the loop. | |
203 | s, err := ParseStats(f) | |
204 | _ = f.Close() | |
205 | if err != nil { | |
206 | return nil, err | |
207 | } | |
208 | ||
209 | s.Name = name | |
210 | stats = append(stats, s) | |
211 | } | |
212 | ||
213 | return stats, nil | |
214 | } |
0 | // Copyright 2019 The Prometheus Authors | |
1 | // Licensed under the Apache License, Version 2.0 (the "License"); | |
2 | // you may not use this file except in compliance with the License. | |
3 | // You may obtain a copy of the License at | |
4 | // | |
5 | // http://www.apache.org/licenses/LICENSE-2.0 | |
6 | // | |
7 | // Unless required by applicable law or agreed to in writing, software | |
8 | // distributed under the License is distributed on an "AS IS" BASIS, | |
9 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
10 | // See the License for the specific language governing permissions and | |
11 | // limitations under the License. | |
12 | ||
13 | // Package xfs provides access to statistics exposed by the XFS filesystem. | |
14 | package xfs_test | |
15 | ||
16 | import ( | |
17 | "testing" | |
18 | ||
19 | "github.com/prometheus/procfs/xfs" | |
20 | ) | |
21 | ||
22 | func TestReadProcStat(t *testing.T) { | |
23 | stats, err := xfs.ReadProcStat("../fixtures/proc") | |
24 | if err != nil { | |
25 | t.Fatalf("failed to parse XFS stats: %v", err) | |
26 | } | |
27 | ||
28 | // Very lightweight test just to sanity check the path used | |
29 | // to open XFS stats. Heavier tests in package xfs. | |
30 | if want, got := uint32(92447), stats.ExtentAllocation.ExtentsAllocated; want != got { | |
31 | t.Errorf("unexpected extents allocated:\nwant: %d\nhave: %d", want, got) | |
32 | } | |
33 | } | |
34 | ||
35 | func TestReadSysStats(t *testing.T) { | |
36 | stats, err := xfs.ReadSysStats("../fixtures/sys") | |
37 | if err != nil { | |
38 | t.Fatalf("failed to parse XFS stats: %v", err) | |
39 | } | |
40 | ||
41 | tests := []struct { | |
42 | name string | |
43 | allocated uint32 | |
44 | }{ | |
45 | { | |
46 | name: "sda1", | |
47 | allocated: 1, | |
48 | }, | |
49 | { | |
50 | name: "sdb1", | |
51 | allocated: 2, | |
52 | }, | |
53 | } | |
54 | ||
55 | const expect = 2 | |
56 | ||
57 | if l := len(stats); l != expect { | |
58 | t.Fatalf("unexpected number of XFS stats: %d", l) | |
59 | } | |
60 | if l := len(tests); l != expect { | |
61 | t.Fatalf("unexpected number of tests: %d", l) | |
62 | } | |
63 | ||
64 | for i, tt := range tests { | |
65 | if want, got := tt.name, stats[i].Name; want != got { | |
66 | t.Errorf("unexpected stats name:\nwant: %q\nhave: %q", want, got) | |
67 | } | |
68 | ||
69 | if want, got := tt.allocated, stats[i].ExtentAllocation.ExtentsAllocated; want != got { | |
70 | t.Errorf("unexpected extents allocated:\nwant: %d\nhave: %d", want, got) | |
71 | } | |
72 | } | |
73 | } |