Codebase list golang-procfs / 02631e2
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
17 changed file(s) with 475 addition(s) and 524 deletion(s). Raw diff Collapse all Expand all
2323 "strings"
2424 )
2525
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
2651 // ParsePseudoFloat parses the peculiar format produced by bcache's bch_hprint.
2752 func parsePseudoFloat(str string) (float64, error) {
2853 ss := strings.Split(str, ".")
1616 "math"
1717 "testing"
1818 )
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 }
1961
2062 func TestDehumanizeTests(t *testing.T) {
2163 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
-81
diskstats.go less more
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
-42
diskstats_test.go less more
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 }
1616 "fmt"
1717 "os"
1818 "path"
19
20 "github.com/prometheus/procfs/nfs"
21 "github.com/prometheus/procfs/xfs"
2219 )
2320
2421 // FS represents the pseudo-filesystem proc, which provides an interface to
4643 func (fs FS) Path(p ...string) string {
4744 return path.Join(append([]string{string(fs)}, p...)...)
4845 }
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 }
2727 t.Error("want NewFS to fail if mount point is not a directory")
2828 }
2929 }
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
-67
iostats/iostats.go less more
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 }
1313 // Package nfs implements parsing of /proc/net/rpc/nfsd.
1414 // Fields are documented in https://www.svennd.be/nfsd-stats-explained-procnetrpcnfsd/
1515 package nfs
16
17 import (
18 "os"
19 "path"
20 )
1621
1722 // ReplyCache models the "rc" line.
1823 type ReplyCache struct {
260265 ServerV4Stats ServerV4Stats
261266 V4Ops V4Ops
262267 }
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
-81
sysfs/block_device.go less more
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
-58
sysfs/block_device_test.go less more
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 }
1616 "fmt"
1717 "os"
1818 "path/filepath"
19
20 "github.com/prometheus/procfs/bcache"
21 "github.com/prometheus/procfs/xfs"
2219 )
2320
2421 // FS represents the pseudo-filesystem sys, which provides an interface to
4643 func (fs FS) Path(p ...string) string {
4744 return filepath.Join(append([]string{string(fs)}, p...)...)
4845 }
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 }
2727 t.Error("want NewFS to fail if mount point is not a directory")
2828 }
2929 }
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 }
1717 "strings"
1818 "testing"
1919
20 "github.com/prometheus/procfs"
2120 "github.com/prometheus/procfs/xfs"
2221 )
2322
424423 stats, err = xfs.ParseStats(strings.NewReader(tt.s))
425424 }
426425 if tt.fs {
427 stats, err = procfs.FS("../fixtures/proc").XFSStats()
426 stats, err = xfs.ReadProcStat("../fixtures/proc")
428427 }
429428
430429 if tt.invalid && err == nil {
1212
1313 // Package xfs provides access to statistics exposed by the XFS filesystem.
1414 package xfs
15
16 import (
17 "os"
18 "path"
19 "path/filepath"
20 )
1521
1622 // Stats contains XFS filesystem runtime statistics, parsed from
1723 // /proc/fs/xfs/stat.
160166 WriteBytes uint64
161167 ReadBytes uint64
162168 }
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 }