diff --git a/bcache/get.go b/bcache/get.go index b6d97de15..3f955fa03 100644 --- a/bcache/get.go +++ b/bcache/get.go @@ -24,6 +24,31 @@ import ( "strings" ) +// ReadStats retrieves bcache runtime statistics for each bcache. +func ReadStats(sysfs string) ([]*Stats, error) { + matches, err := filepath.Glob(path.Join(sysfs, "fs/bcache/*-*")) + if err != nil { + return nil, err + } + + stats := make([]*Stats, 0, len(matches)) + for _, uuidPath := range matches { + // "*-*" in glob above indicates the name of the bcache. + name := filepath.Base(uuidPath) + + // stats + s, err := GetStats(uuidPath) + if err != nil { + return nil, err + } + + s.Name = name + stats = append(stats, s) + } + + return stats, nil +} + // ParsePseudoFloat parses the peculiar format produced by bcache's bch_hprint. func parsePseudoFloat(str string) (float64, error) { ss := strings.Split(str, ".") diff --git a/bcache/get_test.go b/bcache/get_test.go index 1d41a5ad3..5bea2f7de 100644 --- a/bcache/get_test.go +++ b/bcache/get_test.go @@ -18,6 +18,48 @@ import ( "testing" ) +func TestFSBcacheStats(t *testing.T) { + stats, err := ReadStats("../fixtures/sys") + if err != nil { + t.Fatalf("failed to parse bcache stats: %v", err) + } + + tests := []struct { + name string + bdevs int + caches int + }{ + { + name: "deaddd54-c735-46d5-868e-f331c5fd7c74", + bdevs: 1, + caches: 1, + }, + } + + const expect = 1 + + if l := len(stats); l != expect { + t.Fatalf("unexpected number of bcache stats: %d", l) + } + if l := len(tests); l != expect { + t.Fatalf("unexpected number of tests: %d", l) + } + + for i, tt := range tests { + if want, got := tt.name, stats[i].Name; want != got { + t.Errorf("unexpected stats name:\nwant: %q\nhave: %q", want, got) + } + + if want, got := tt.bdevs, len(stats[i].Bdevs); want != got { + t.Errorf("unexpected value allocated:\nwant: %d\nhave: %d", want, got) + } + + if want, got := tt.caches, len(stats[i].Caches); want != got { + t.Errorf("unexpected value allocated:\nwant: %d\nhave: %d", want, got) + } + } +} + func TestDehumanizeTests(t *testing.T) { dehumanizeTests := []struct { in []byte diff --git a/blockdevice/stats.go b/blockdevice/stats.go new file mode 100644 index 000000000..313152985 --- /dev/null +++ b/blockdevice/stats.go @@ -0,0 +1,181 @@ +// Copyright 2018 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package blockdevice + +import ( + "bufio" + "fmt" + "io" + "io/ioutil" + "os" + "path" + "strings" +) + +// Info contains identifying information for a block device such as a disk drive +type Info struct { + MajorNumber uint32 + MinorNumber uint32 + DeviceName string +} + +// IOStats models the iostats data described in the kernel documentation +// https://www.kernel.org/doc/Documentation/iostats.txt, +// https://www.kernel.org/doc/Documentation/block/stat.txt, +// and https://www.kernel.org/doc/Documentation/ABI/testing/procfs-diskstats +type IOStats struct { + // ReadIOs is the number of reads completed successfully. + ReadIOs uint64 + // ReadMerges is the number of reads merged. Reads and writes + // which are adjacent to each other may be merged for efficiency. + ReadMerges uint64 + // ReadSectors is the total number of sectors read successfully. + ReadSectors uint64 + // ReadTicks is the total number of milliseconds spent by all reads. + ReadTicks uint64 + // WriteIOs is the total number of writes completed successfully. + WriteIOs uint64 + // WriteMerges is the number of reads merged. + WriteMerges uint64 + // WriteSectors is the total number of sectors written successfully. + WriteSectors uint64 + // WriteTicks is the total number of milliseconds spent by all writes. + WriteTicks uint64 + // IOsInProgress is number of I/Os currently in progress. + IOsInProgress uint64 + // IOsTotalTicks is the number of milliseconds spent doing I/Os. + // This field increases so long as IosInProgress is nonzero. + IOsTotalTicks uint64 + // WeightedIOTicks is the weighted number of milliseconds spent doing I/Os. + // This can also be used to estimate average queue wait time for requests. + WeightedIOTicks uint64 + // DiscardIOs is the total number of discards completed successfully. + DiscardIOs uint64 + // DiscardMerges is the number of discards merged. + DiscardMerges uint64 + // DiscardSectors is the total number of sectors discarded successfully. + DiscardSectors uint64 + // DiscardTicks is the total number of milliseconds spent by all discards. + DiscardTicks uint64 +} + +// Diskstats combines the device Info and IOStats +type Diskstats struct { + Info + IOStats + // IoStatsCount contains the number of io stats read. For kernel versions + // 4.18+, there should be 18 fields read. For earlier kernel versions this + // will be 14 because the discard values are not available. + IoStatsCount int +} + +const ( + procDiskstatsPath = "diskstats" + procDiskstatsFormat = "%d %d %s %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d" + sysBlockPath = "block" + sysBlockStatFormat = "%d %d %d %d %d %d %d %d %d %d %d %d %d %d %d" +) + +// ReadProcDiskstats reads the diskstats file and returns +// an array of Diskstats (one per line/device) +func ReadProcDiskstats(procfs string) ([]Diskstats, error) { + file, err := os.Open(path.Join(procfs, procDiskstatsPath)) + if err != nil { + return nil, err + } + defer file.Close() + + diskstats := []Diskstats{} + scanner := bufio.NewScanner(file) + for scanner.Scan() { + d := &Diskstats{} + d.IoStatsCount, err = fmt.Sscanf(scanner.Text(), procDiskstatsFormat, + &d.MajorNumber, + &d.MinorNumber, + &d.DeviceName, + &d.ReadIOs, + &d.ReadMerges, + &d.ReadSectors, + &d.ReadTicks, + &d.WriteIOs, + &d.WriteMerges, + &d.WriteSectors, + &d.WriteTicks, + &d.IOsInProgress, + &d.IOsTotalTicks, + &d.WeightedIOTicks, + &d.DiscardIOs, + &d.DiscardMerges, + &d.DiscardSectors, + &d.DiscardTicks, + ) + // The io.EOF error can be safely ignored because it just means we read fewer than + // the full 18 fields. + if err != nil && err != io.EOF { + return diskstats, err + } + if d.IoStatsCount == 14 || d.IoStatsCount == 18 { + diskstats = append(diskstats, *d) + } + } + return diskstats, scanner.Err() +} + +// ListSysBlockDevices lists the device names from /sys/block/ +func ListSysBlockDevices(sysfs string) ([]string, error) { + deviceDirs, err := ioutil.ReadDir(path.Join(sysfs, sysBlockPath)) + if err != nil { + return nil, err + } + devices := []string{} + for _, deviceDir := range deviceDirs { + if deviceDir.IsDir() { + devices = append(devices, deviceDir.Name()) + } + } + return devices, nil +} + +// ReadSysBlockDeviceStat returns stats for the block device read from /sys/block//stat. +// The number of stats read will be 15 if the discard stats are available (kernel 4.18+) +// and 11 if they are not available. +func ReadSysBlockDeviceStat(sysfs string, device string) (IOStats, int, error) { + stat := IOStats{} + bytes, err := ioutil.ReadFile(path.Join(sysfs, sysBlockPath, device, "stat")) + if err != nil { + return stat, 0, err + } + count, err := fmt.Sscanf(strings.TrimSpace(string(bytes)), sysBlockStatFormat, + &stat.ReadIOs, + &stat.ReadMerges, + &stat.ReadSectors, + &stat.ReadTicks, + &stat.WriteIOs, + &stat.WriteMerges, + &stat.WriteSectors, + &stat.WriteTicks, + &stat.IOsInProgress, + &stat.IOsTotalTicks, + &stat.WeightedIOTicks, + &stat.DiscardIOs, + &stat.DiscardMerges, + &stat.DiscardSectors, + &stat.DiscardTicks, + ) + // An io.EOF error is ignored because it just means we read fewer than the full 15 fields. + if err == io.EOF { + return stat, count, nil + } + return stat, count, err +} diff --git a/blockdevice/stats_test.go b/blockdevice/stats_test.go new file mode 100644 index 000000000..e5a7b8cf1 --- /dev/null +++ b/blockdevice/stats_test.go @@ -0,0 +1,90 @@ +// Copyright 2018 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package blockdevice + +import ( + "testing" +) + +const ( + failMsgFormat = "%v, expected %v, actual %v" + procfsFixtures = "../fixtures/proc" + sysfsFixtures = "../fixtures/sys" +) + +func TestDiskstats(t *testing.T) { + diskstats, err := ReadProcDiskstats(procfsFixtures) + if err != nil { + t.Fatal(err) + } + expectedNumOfDevices := 49 + if len(diskstats) != expectedNumOfDevices { + t.Errorf(failMsgFormat, "Incorrect number of devices", expectedNumOfDevices, len(diskstats)) + } + if diskstats[0].DeviceName != "ram0" { + t.Errorf(failMsgFormat, "Incorrect device name", "ram0", diskstats[0].DeviceName) + } + if diskstats[1].IoStatsCount != 14 { + t.Errorf(failMsgFormat, "Incorrect number of stats read", 14, diskstats[0].IoStatsCount) + } + if diskstats[24].WriteIOs != 28444756 { + t.Errorf(failMsgFormat, "Incorrect writes completed", 28444756, diskstats[24].WriteIOs) + } + if diskstats[48].DiscardTicks != 11130 { + t.Errorf(failMsgFormat, "Incorrect discard time", 11130, diskstats[48].DiscardTicks) + } + if diskstats[48].IoStatsCount != 18 { + t.Errorf(failMsgFormat, "Incorrect number of stats read", 18, diskstats[48].IoStatsCount) + } +} + +func TestBlockDevice(t *testing.T) { + devices, err := ListSysBlockDevices(sysfsFixtures) + if err != nil { + t.Fatal(err) + } + expectedNumOfDevices := 2 + if len(devices) != expectedNumOfDevices { + t.Fatalf(failMsgFormat, "Incorrect number of devices", expectedNumOfDevices, len(devices)) + } + if devices[0] != "dm-0" { + t.Errorf(failMsgFormat, "Incorrect device name", "dm-0", devices[0]) + } + device0stats, count, err := ReadSysBlockDeviceStat(sysfsFixtures, devices[0]) + if err != nil { + t.Fatal(err) + } + if count != 11 { + t.Errorf(failMsgFormat, "Incorrect number of stats read", 11, count) + } + if device0stats.ReadIOs != 6447303 { + t.Errorf(failMsgFormat, "Incorrect read I/Os", 6447303, device0stats.ReadIOs) + } + if device0stats.WeightedIOTicks != 6088971 { + t.Errorf(failMsgFormat, "Incorrect time in queue", 6088971, device0stats.WeightedIOTicks) + } + device1stats, count, err := ReadSysBlockDeviceStat(sysfsFixtures, devices[1]) + if count != 15 { + t.Errorf(failMsgFormat, "Incorrect number of stats read", 15, count) + } + if err != nil { + t.Fatal(err) + } + if device1stats.WriteSectors != 286915323 { + t.Errorf(failMsgFormat, "Incorrect write merges", 286915323, device1stats.WriteSectors) + } + if device1stats.DiscardTicks != 12 { + t.Errorf(failMsgFormat, "Incorrect discard ticks", 12, device1stats.DiscardTicks) + } +} diff --git a/diskstats.go b/diskstats.go deleted file mode 100644 index a116cf395..000000000 --- a/diskstats.go +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2018 The Prometheus Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package procfs - -import ( - "bufio" - "fmt" - "io" - "os" - - "github.com/prometheus/procfs/iostats" -) - -const ( - diskstatsFilename = "diskstats" - statFormat = "%d %d %s %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d" -) - -// NewDiskstats reads the diskstats file and returns -// an array of Diskstats (one per line/device) -func NewDiskstats() ([]iostats.IODeviceStats, error) { - fs, err := NewFS(DefaultMountPoint) - if err != nil { - return nil, err - } - - return fs.NewDiskstats() -} - -// NewDiskstats reads the diskstats file and returns -// an array of Diskstats (one per line/device) -func (fs FS) NewDiskstats() ([]iostats.IODeviceStats, error) { - file, err := os.Open(fs.Path(diskstatsFilename)) - if err != nil { - return nil, err - } - defer file.Close() - - diskstats := []iostats.IODeviceStats{} - scanner := bufio.NewScanner(file) - for scanner.Scan() { - d := &iostats.IODeviceStats{} - count, err := fmt.Sscanf(scanner.Text(), statFormat, - &d.MajorNumber, - &d.MinorNumber, - &d.DeviceName, - &d.ReadIOs, - &d.ReadMerges, - &d.ReadSectors, - &d.ReadTicks, - &d.WriteIOs, - &d.WriteMerges, - &d.WriteSectors, - &d.WriteTicks, - &d.IOsInProgress, - &d.IOsTotalTicks, - &d.WeightedIOTicks, - &d.DiscardIOs, - &d.DiscardMerges, - &d.DiscardSectors, - &d.DiscardTicks) - if err != nil && err != io.EOF { - return diskstats, err - } - if count == 14 || count == 18 { - diskstats = append(diskstats, *d) - } - } - return diskstats, nil -} diff --git a/diskstats_test.go b/diskstats_test.go deleted file mode 100644 index b9608fdda..000000000 --- a/diskstats_test.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2018 The Prometheus Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package procfs - -import ( - "testing" -) - -const ( - failMsgFormat = "%v, expected %v, actual %v" - expectedNumOfDevices = 49 -) - -func TestDiskstats(t *testing.T) { - diskstats, err := FS(procTestFixtures).NewDiskstats() - if err != nil { - t.Fatal(err) - } - if len(diskstats) != expectedNumOfDevices { - t.Errorf(failMsgFormat, "Incorrect number of devices", expectedNumOfDevices, len(diskstats)) - } - if diskstats[0].DeviceName != "ram0" { - t.Errorf(failMsgFormat, "Incorrect device name", "ram0", diskstats[0].DeviceName) - } - if diskstats[24].WriteIOs != 28444756 { - t.Errorf(failMsgFormat, "Incorrect writes completed", 28444756, diskstats[24].WriteIOs) - } - if diskstats[48].DiscardTicks != 11130 { - t.Errorf(failMsgFormat, "Incorrect discard time", 11130, diskstats[48].DiscardTicks) - } -} diff --git a/fs.go b/fs.go index b6c6b2ce1..f7a151cc7 100644 --- a/fs.go +++ b/fs.go @@ -17,9 +17,6 @@ import ( "fmt" "os" "path" - - "github.com/prometheus/procfs/nfs" - "github.com/prometheus/procfs/xfs" ) // FS represents the pseudo-filesystem proc, which provides an interface to @@ -47,36 +44,3 @@ func NewFS(mountPoint string) (FS, error) { func (fs FS) Path(p ...string) string { return path.Join(append([]string{string(fs)}, p...)...) } - -// XFSStats retrieves XFS filesystem runtime statistics. -func (fs FS) XFSStats() (*xfs.Stats, error) { - f, err := os.Open(fs.Path("fs/xfs/stat")) - if err != nil { - return nil, err - } - defer f.Close() - - return xfs.ParseStats(f) -} - -// NFSClientRPCStats retrieves NFS client RPC statistics. -func (fs FS) NFSClientRPCStats() (*nfs.ClientRPCStats, error) { - f, err := os.Open(fs.Path("net/rpc/nfs")) - if err != nil { - return nil, err - } - defer f.Close() - - return nfs.ParseClientRPCStats(f) -} - -// NFSdServerRPCStats retrieves NFS daemon RPC statistics. -func (fs FS) NFSdServerRPCStats() (*nfs.ServerRPCStats, error) { - f, err := os.Open(fs.Path("net/rpc/nfsd")) - if err != nil { - return nil, err - } - defer f.Close() - - return nfs.ParseServerRPCStats(f) -} diff --git a/fs_test.go b/fs_test.go index 0927ab9dc..ce16fae7a 100644 --- a/fs_test.go +++ b/fs_test.go @@ -28,16 +28,3 @@ func TestNewFS(t *testing.T) { t.Error("want NewFS to fail if mount point is not a directory") } } - -func TestFSXFSStats(t *testing.T) { - stats, err := FS(procTestFixtures).XFSStats() - if err != nil { - t.Fatalf("failed to parse XFS stats: %v", err) - } - - // Very lightweight test just to sanity check the path used - // to open XFS stats. Heavier tests in package xfs. - if want, got := uint32(92447), stats.ExtentAllocation.ExtentsAllocated; want != got { - t.Errorf("unexpected extents allocated:\nwant: %d\nhave: %d", want, got) - } -} diff --git a/iostats/iostats.go b/iostats/iostats.go deleted file mode 100644 index 97e831476..000000000 --- a/iostats/iostats.go +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2018 The Prometheus Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package iostats - -// IODevice contains identifying information for an I/O device -type IODevice struct { - MajorNumber uint32 - MinorNumber uint32 - DeviceName string -} - -// IOStats models the iostats data described in the kernel documentation -// https://www.kernel.org/doc/Documentation/iostats.txt, -// https://www.kernel.org/doc/Documentation/block/stat.txt, -// and https://www.kernel.org/doc/Documentation/ABI/testing/procfs-diskstats -type IOStats struct { - // ReadIOs is the number of reads completed successfully. - ReadIOs uint64 - // ReadMerges is the number of reads merged. Reads and writes - // which are adjacent to each other may be merged for efficiency. - ReadMerges uint64 - // ReadSectors is the total number of sectors read successfully. - ReadSectors uint64 - // ReadTicks is the total number of milliseconds spent by all reads. - ReadTicks uint64 - // WriteIOs is the total number of writes completed successfully. - WriteIOs uint64 - // WriteMerges is the number of reads merged. - WriteMerges uint64 - // WriteSectors is the total number of sectors written successfully. - WriteSectors uint64 - // WriteTicks is the total number of milliseconds spent by all writes. - WriteTicks uint64 - // IOsInProgress is number of I/Os currently in progress. - IOsInProgress uint64 - // IOsTotalTicks is the number of milliseconds spent doing I/Os. - // This field increases so long as IosInProgress is nonzero. - IOsTotalTicks uint64 - // WeightedIOTicks is the weighted number of milliseconds spent doing I/Os. - // This can also be used to estimate average queue wait time for requests. - WeightedIOTicks uint64 - // DiscardIOs is the total number of discards completed successfully. - DiscardIOs uint64 - // DiscardMerges is the number of discards merged. - DiscardMerges uint64 - // DiscardSectors is the total number of sectors discarded successfully. - DiscardSectors uint64 - // DiscardTicks is the total number of milliseconds spent by all discards. - DiscardTicks uint64 -} - -// IODeviceStats combines IODevice and IOStats -type IODeviceStats struct { - IODevice - IOStats -} diff --git a/nfs/nfs.go b/nfs/nfs.go index 651bf6819..4c8dad42b 100644 --- a/nfs/nfs.go +++ b/nfs/nfs.go @@ -15,6 +15,11 @@ // Fields are documented in https://www.svennd.be/nfsd-stats-explained-procnetrpcnfsd/ package nfs +import ( + "os" + "path" +) + // ReplyCache models the "rc" line. type ReplyCache struct { Hits uint64 @@ -261,3 +266,27 @@ type ServerRPCStats struct { ServerV4Stats ServerV4Stats V4Ops V4Ops } + +// ReadClientRPCStats retrieves NFS client RPC statistics +// from proc/net/rpc/nfs. +func ReadClientRPCStats(procfs string) (*ClientRPCStats, error) { + f, err := os.Open(path.Join(procfs, "net/rpc/nfs")) + if err != nil { + return nil, err + } + defer f.Close() + + return ParseClientRPCStats(f) +} + +// ReadServerRPCStats retrieves NFS daemon RPC statistics +// from proc/net/rpc/nfsd. +func ReadServerRPCStats(procfs string) (*ServerRPCStats, error) { + f, err := os.Open(path.Join(procfs, "net/rpc/nfsd")) + if err != nil { + return nil, err + } + defer f.Close() + + return ParseServerRPCStats(f) +} diff --git a/sysfs/block_device.go b/sysfs/block_device.go deleted file mode 100644 index 693952996..000000000 --- a/sysfs/block_device.go +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2018 The Prometheus Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// +build !windows - -package sysfs - -import ( - "fmt" - "io/ioutil" - "strings" - - "github.com/prometheus/procfs/iostats" -) - -const ( - blockPath = "block" - blockDeviceStatFormat = "%d %d %d %d %d %d %d %d %d %d %d %d %d %d %d" -) - -// BlockDevice represents block device in the sys filesystem -// Docs here: https://www.kernel.org/doc/Documentation/block/ -type BlockDevice struct { - DeviceName string - fs FS -} - -// AllBlockDevices gets the list of block devices from the sys file system -func (fs FS) AllBlockDevices() ([]BlockDevice, error) { - deviceDirs, err := ioutil.ReadDir(fs.Path(blockPath)) - if err != nil { - return nil, err - } - devices := []BlockDevice{} - for _, deviceDir := range deviceDirs { - if deviceDir.IsDir() { - devices = append(devices, BlockDevice{deviceDir.Name(), fs}) - } - } - return devices, nil -} - -// NewBlockDeviceStat returns stats for the block device read from /sys/block//stat. -func (d BlockDevice) NewBlockDeviceStat() (iostats.IOStats, error) { - stat := iostats.IOStats{} - bytes, err := ioutil.ReadFile(d.fs.Path(blockPath, d.DeviceName, "stat")) - if err != nil { - return stat, err - } - count, err := fmt.Sscanf(strings.TrimSpace(string(bytes)), blockDeviceStatFormat, - &stat.ReadIOs, - &stat.ReadMerges, - &stat.ReadSectors, - &stat.ReadTicks, - &stat.WriteIOs, - &stat.WriteMerges, - &stat.WriteSectors, - &stat.WriteTicks, - &stat.IOsInProgress, - &stat.IOsTotalTicks, - &stat.WeightedIOTicks, - &stat.DiscardIOs, - &stat.DiscardMerges, - &stat.DiscardSectors, - &stat.DiscardTicks, - ) - if count == 11 || count == 15 { - return stat, nil - } - return iostats.IOStats{}, err -} diff --git a/sysfs/block_device_test.go b/sysfs/block_device_test.go deleted file mode 100644 index c24a8044f..000000000 --- a/sysfs/block_device_test.go +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2018 The Prometheus Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// +build !windows - -package sysfs - -import ( - "testing" -) - -const ( - failMsgFormat = "%v, expected %v, actual %v" - expectedNumOfDevices = 2 -) - -func TestBlockDevice(t *testing.T) { - devices, err := FS(sysTestFixtures).AllBlockDevices() - if err != nil { - t.Fatal(err) - } - if len(devices) != expectedNumOfDevices { - t.Fatalf(failMsgFormat, "Incorrect number of devices", expectedNumOfDevices, len(devices)) - } - if devices[0].DeviceName != "dm-0" { - t.Errorf(failMsgFormat, "Incorrect device name", "dm-0", devices[0].DeviceName) - } - device0stats, err := devices[0].NewBlockDeviceStat() - if err != nil { - t.Fatal(err) - } - if device0stats.ReadIOs != 6447303 { - t.Errorf(failMsgFormat, "Incorrect read I/Os", 6447303, device0stats.ReadIOs) - } - if device0stats.WeightedIOTicks != 6088971 { - t.Errorf(failMsgFormat, "Incorrect time in queue", 6088971, device0stats.WeightedIOTicks) - } - device1stats, err := devices[1].NewBlockDeviceStat() - if err != nil { - t.Fatal(err) - } - if device1stats.WriteSectors != 286915323 { - t.Errorf(failMsgFormat, "Incorrect write merges", 286915323, device1stats.WriteSectors) - } - if device1stats.DiscardTicks != 12 { - t.Errorf(failMsgFormat, "Incorrect discard ticks", 12, device1stats.DiscardTicks) - } -} diff --git a/sysfs/fs.go b/sysfs/fs.go index fb15d438a..8dd258de3 100644 --- a/sysfs/fs.go +++ b/sysfs/fs.go @@ -17,9 +17,6 @@ import ( "fmt" "os" "path/filepath" - - "github.com/prometheus/procfs/bcache" - "github.com/prometheus/procfs/xfs" ) // FS represents the pseudo-filesystem sys, which provides an interface to @@ -47,62 +44,3 @@ func NewFS(mountPoint string) (FS, error) { func (fs FS) Path(p ...string) string { return filepath.Join(append([]string{string(fs)}, p...)...) } - -// XFSStats retrieves XFS filesystem runtime statistics for each mounted XFS -// filesystem. Only available on kernel 4.4+. On older kernels, an empty -// slice of *xfs.Stats will be returned. -func (fs FS) XFSStats() ([]*xfs.Stats, error) { - matches, err := filepath.Glob(fs.Path("fs/xfs/*/stats/stats")) - if err != nil { - return nil, err - } - - stats := make([]*xfs.Stats, 0, len(matches)) - for _, m := range matches { - f, err := os.Open(m) - if err != nil { - return nil, err - } - - // "*" used in glob above indicates the name of the filesystem. - name := filepath.Base(filepath.Dir(filepath.Dir(m))) - - // File must be closed after parsing, regardless of success or - // failure. Defer is not used because of the loop. - s, err := xfs.ParseStats(f) - _ = f.Close() - if err != nil { - return nil, err - } - - s.Name = name - stats = append(stats, s) - } - - return stats, nil -} - -// BcacheStats retrieves bcache runtime statistics for each bcache. -func (fs FS) BcacheStats() ([]*bcache.Stats, error) { - matches, err := filepath.Glob(fs.Path("fs/bcache/*-*")) - if err != nil { - return nil, err - } - - stats := make([]*bcache.Stats, 0, len(matches)) - for _, uuidPath := range matches { - // "*-*" in glob above indicates the name of the bcache. - name := filepath.Base(uuidPath) - - // stats - s, err := bcache.GetStats(uuidPath) - if err != nil { - return nil, err - } - - s.Name = name - stats = append(stats, s) - } - - return stats, nil -} diff --git a/sysfs/fs_test.go b/sysfs/fs_test.go index 33a17419c..2ef5a733e 100644 --- a/sysfs/fs_test.go +++ b/sysfs/fs_test.go @@ -27,86 +27,8 @@ func TestNewFS(t *testing.T) { if _, err := NewFS("doc.go"); err == nil { t.Error("want NewFS to fail if mount point is not a directory") } -} - -func TestFSXFSStats(t *testing.T) { - stats, err := FS(sysTestFixtures).XFSStats() - if err != nil { - t.Fatalf("failed to parse XFS stats: %v", err) - } - - tests := []struct { - name string - allocated uint32 - }{ - { - name: "sda1", - allocated: 1, - }, - { - name: "sdb1", - allocated: 2, - }, - } - - const expect = 2 - - if l := len(stats); l != expect { - t.Fatalf("unexpected number of XFS stats: %d", l) - } - if l := len(tests); l != expect { - t.Fatalf("unexpected number of tests: %d", l) - } - - for i, tt := range tests { - if want, got := tt.name, stats[i].Name; want != got { - t.Errorf("unexpected stats name:\nwant: %q\nhave: %q", want, got) - } - - if want, got := tt.allocated, stats[i].ExtentAllocation.ExtentsAllocated; want != got { - t.Errorf("unexpected extents allocated:\nwant: %d\nhave: %d", want, got) - } - } -} - -func TestFSBcacheStats(t *testing.T) { - stats, err := FS(sysTestFixtures).BcacheStats() - if err != nil { - t.Fatalf("failed to parse bcache stats: %v", err) - } - - tests := []struct { - name string - bdevs int - caches int - }{ - { - name: "deaddd54-c735-46d5-868e-f331c5fd7c74", - bdevs: 1, - caches: 1, - }, - } - - const expect = 1 - - if l := len(stats); l != expect { - t.Fatalf("unexpected number of bcache stats: %d", l) - } - if l := len(tests); l != expect { - t.Fatalf("unexpected number of tests: %d", l) - } - - for i, tt := range tests { - if want, got := tt.name, stats[i].Name; want != got { - t.Errorf("unexpected stats name:\nwant: %q\nhave: %q", want, got) - } - - if want, got := tt.bdevs, len(stats[i].Bdevs); want != got { - t.Errorf("unexpected value allocated:\nwant: %d\nhave: %d", want, got) - } - if want, got := tt.caches, len(stats[i].Caches); want != got { - t.Errorf("unexpected value allocated:\nwant: %d\nhave: %d", want, got) - } + if _, err := NewFS(sysTestFixtures); err != nil { + t.Error("want NewFS to succeed if mount point exists") } } diff --git a/xfs/parse_test.go b/xfs/parse_test.go index cd4dcbbe4..e2208f70a 100644 --- a/xfs/parse_test.go +++ b/xfs/parse_test.go @@ -18,7 +18,6 @@ import ( "strings" "testing" - "github.com/prometheus/procfs" "github.com/prometheus/procfs/xfs" ) @@ -425,7 +424,7 @@ func TestParseStats(t *testing.T) { stats, err = xfs.ParseStats(strings.NewReader(tt.s)) } if tt.fs { - stats, err = procfs.FS("../fixtures/proc").XFSStats() + stats, err = xfs.ReadProcStat("../fixtures/proc") } if tt.invalid && err == nil { diff --git a/xfs/xfs.go b/xfs/xfs.go index d86794b7c..4bb793fc1 100644 --- a/xfs/xfs.go +++ b/xfs/xfs.go @@ -14,6 +14,12 @@ // Package xfs provides access to statistics exposed by the XFS filesystem. package xfs +import ( + "os" + "path" + "path/filepath" +) + // Stats contains XFS filesystem runtime statistics, parsed from // /proc/fs/xfs/stat. // @@ -161,3 +167,49 @@ type ExtendedPrecisionStats struct { WriteBytes uint64 ReadBytes uint64 } + +// ReadProcStat retrieves XFS filesystem runtime statistics +// from proc/fs/xfs/stat given the profs mount point. +func ReadProcStat(procfs string) (*Stats, error) { + f, err := os.Open(path.Join(procfs, "fs/xfs/stat")) + if err != nil { + return nil, err + } + defer f.Close() + + return ParseStats(f) +} + +// ReadSysStats retrieves XFS filesystem runtime statistics for each mounted XFS +// filesystem. Only available on kernel 4.4+. On older kernels, an empty +// slice of *xfs.Stats will be returned. +func ReadSysStats(sysfs string) ([]*Stats, error) { + matches, err := filepath.Glob(path.Join(sysfs, "fs/xfs/*/stats/stats")) + if err != nil { + return nil, err + } + + stats := make([]*Stats, 0, len(matches)) + for _, m := range matches { + f, err := os.Open(m) + if err != nil { + return nil, err + } + + // "*" used in glob above indicates the name of the filesystem. + name := filepath.Base(filepath.Dir(filepath.Dir(m))) + + // File must be closed after parsing, regardless of success or + // failure. Defer is not used because of the loop. + s, err := ParseStats(f) + _ = f.Close() + if err != nil { + return nil, err + } + + s.Name = name + stats = append(stats, s) + } + + return stats, nil +} diff --git a/xfs/xfs_test.go b/xfs/xfs_test.go new file mode 100644 index 000000000..d6f1fcb46 --- /dev/null +++ b/xfs/xfs_test.go @@ -0,0 +1,74 @@ +// Copyright 2019 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package xfs provides access to statistics exposed by the XFS filesystem. +package xfs_test + +import ( + "testing" + + "github.com/prometheus/procfs/xfs" +) + +func TestReadProcStat(t *testing.T) { + stats, err := xfs.ReadProcStat("../fixtures/proc") + if err != nil { + t.Fatalf("failed to parse XFS stats: %v", err) + } + + // Very lightweight test just to sanity check the path used + // to open XFS stats. Heavier tests in package xfs. + if want, got := uint32(92447), stats.ExtentAllocation.ExtentsAllocated; want != got { + t.Errorf("unexpected extents allocated:\nwant: %d\nhave: %d", want, got) + } +} + +func TestReadSysStats(t *testing.T) { + stats, err := xfs.ReadSysStats("../fixtures/sys") + if err != nil { + t.Fatalf("failed to parse XFS stats: %v", err) + } + + tests := []struct { + name string + allocated uint32 + }{ + { + name: "sda1", + allocated: 1, + }, + { + name: "sdb1", + allocated: 2, + }, + } + + const expect = 2 + + if l := len(stats); l != expect { + t.Fatalf("unexpected number of XFS stats: %d", l) + } + if l := len(tests); l != expect { + t.Fatalf("unexpected number of tests: %d", l) + } + + for i, tt := range tests { + if want, got := tt.name, stats[i].Name; want != got { + t.Errorf("unexpected stats name:\nwant: %q\nhave: %q", want, got) + } + + if want, got := tt.allocated, stats[i].ExtentAllocation.ExtentsAllocated; want != got { + t.Errorf("unexpected extents allocated:\nwant: %d\nhave: %d", want, got) + } + } +}