diff --git a/btrfs/btrfs.go b/btrfs/btrfs.go index 094c898a..5b4ff350 100644 --- a/btrfs/btrfs.go +++ b/btrfs/btrfs.go @@ -25,6 +25,7 @@ type Stats struct { NodeSize uint64 QuotaOverride uint64 SectorSize uint64 + CommitStats CommitStats } // Allocation contains allocation statistics for data, metadata and system data. @@ -65,3 +66,12 @@ type LayoutUsage struct { type Device struct { Size uint64 } + +// Number of commits and various time related statistics. +// See Linux fs/btrfs/sysfs.c with 6.x version. +type CommitStats struct { + Commits uint64 + LastCommitMs uint64 + MaxCommitMs uint64 + TotalCommitMs uint64 +} diff --git a/btrfs/get.go b/btrfs/get.go index 77c72e12..db0046b6 100644 --- a/btrfs/get.go +++ b/btrfs/get.go @@ -14,6 +14,8 @@ package btrfs import ( + "bufio" + "fmt" "os" "path" "path/filepath" @@ -245,6 +247,62 @@ func (r *reader) readFilesystemStats() (s *Stats) { Metadata: r.readAllocationStats("allocation/metadata"), System: r.readAllocationStats("allocation/system"), }, + + // Read commit stats data + CommitStats: r.readCommitStats("commit_stats"), } return } + +// readCommitStats returns the commit_stats information for commit stats metrics. +func (r *reader) readCommitStats(p string) CommitStats { + stats := CommitStats{} + + f, err := os.Open(path.Join(r.path, p)) + if err != nil { + // if commit_stats not found. maybe btrfs version < 6.0 + if !os.IsNotExist(err) { + r.err = err + } + return stats + } + defer f.Close() + + scanner := bufio.NewScanner(f) + + for scanner.Scan() { + line := scanner.Text() + parts := strings.Fields(scanner.Text()) + // require + if len(parts) != 2 { + r.err = fmt.Errorf("invalid commit_stats line %q", line) + return stats + } + + value, err := strconv.ParseUint(parts[1], 10, 64) + if err != nil { + r.err = fmt.Errorf("error parsing commit_stats line: %w", err) + return stats + } + + switch metricName := parts[0]; metricName { + case "commits": + stats.Commits = value + case "last_commit_ms": + stats.LastCommitMs = value + case "max_commit_ms": + stats.MaxCommitMs = value + case "total_commit_ms": + stats.TotalCommitMs = value + default: + continue + } + } + + if err := scanner.Err(); err != nil { + r.err = fmt.Errorf("error scanning commit_stats file: %w", err) + return stats + } + + return stats +} diff --git a/btrfs/get_test.go b/btrfs/get_test.go index 23059c62..e2d09e1b 100644 --- a/btrfs/get_test.go +++ b/btrfs/get_test.go @@ -19,6 +19,7 @@ type testVector struct { uuid, label string devices, features int data, meta, system alloc + commitstats commit } type alloc struct { @@ -27,6 +28,13 @@ type alloc struct { ratio float64 } +type commit struct { + commits uint64 + lastCommitMs uint64 + maxCommitMs uint64 + totalCommitMs uint64 +} + func TestFSBtrfsStats(t *testing.T) { btrfs, err := NewFS("testdata/fixtures/sys") if err != nil { @@ -39,22 +47,24 @@ func TestFSBtrfsStats(t *testing.T) { tests := []testVector{ { - uuid: "0abb23a9-579b-43e6-ad30-227ef47fcb9d", - label: "fixture", - devices: 2, - features: 4, - data: alloc{"raid0", 2147483648, 1}, - meta: alloc{"raid1", 1073741824, 2}, - system: alloc{"raid1", 8388608, 2}, + uuid: "0abb23a9-579b-43e6-ad30-227ef47fcb9d", + label: "fixture", + devices: 2, + features: 4, + data: alloc{"raid0", 2147483648, 1}, + meta: alloc{"raid1", 1073741824, 2}, + system: alloc{"raid1", 8388608, 2}, + commitstats: commit{258051, 1000, 51462, 47836090}, }, { - uuid: "7f07c59f-6136-449c-ab87-e1cf2328731b", - label: "", - devices: 4, - features: 5, - data: alloc{"raid5", 644087808, 4. / 3.}, - meta: alloc{"raid6", 429391872, 4. / 2.}, - system: alloc{"raid6", 16777216, 4. / 2.}, + uuid: "7f07c59f-6136-449c-ab87-e1cf2328731b", + label: "", + devices: 4, + features: 5, + data: alloc{"raid5", 644087808, 4. / 3.}, + meta: alloc{"raid6", 429391872, 4. / 2.}, + system: alloc{"raid6", 16777216, 4. / 2.}, + commitstats: commit{0, 0, 0, 0}, }, } @@ -98,5 +108,21 @@ func TestFSBtrfsStats(t *testing.T) { if want, got := tt.system.ratio, stats[i].Allocation.System.Layouts[tt.system.layout].Ratio; want != got { t.Errorf("fs %q unexpected system ratio:\nwant: %f\nhave: %f", tt.uuid, want, got) } + + if want, got := tt.commitstats.commits, stats[i].CommitStats.Commits; want != got { + t.Errorf("fs %q unexpected commit stats commits:\nwant: %d\nhave: %d", tt.uuid, want, got) + } + + if want, got := tt.commitstats.lastCommitMs, stats[i].CommitStats.LastCommitMs; want != got { + t.Errorf("fs %q unexpected commit stats last_commit_ms:\nwant: %d\nhave: %d", tt.uuid, want, got) + } + + if want, got := tt.commitstats.maxCommitMs, stats[i].CommitStats.MaxCommitMs; want != got { + t.Errorf("fs %q unexpected commit stats max_commit_ms:\nwant: %d\nhave: %d", tt.uuid, want, got) + } + + if want, got := tt.commitstats.totalCommitMs, stats[i].CommitStats.TotalCommitMs; want != got { + t.Errorf("fs %q unexpected commit stats total_commit_ms:\nwant: %d\nhave: %d", tt.uuid, want, got) + } } } diff --git a/testdata/fixtures.ttar b/testdata/fixtures.ttar index 67065168..b22807ac 100644 --- a/testdata/fixtures.ttar +++ b/testdata/fixtures.ttar @@ -14230,6 +14230,14 @@ Lines: 1 4096 Mode: 444 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Path: fixtures/sys/fs/btrfs/0abb23a9-579b-43e6-ad30-227ef47fcb9d/commit_stats +Lines: 4 +commits 258051 +last_commit_ms 1000 +max_commit_ms 51462 +total_commit_ms 47836090EOF +Mode: 644 +# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Directory: fixtures/sys/fs/btrfs/0abb23a9-579b-43e6-ad30-227ef47fcb9d/devices Mode: 755 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -