diff --git a/bcache/bcache.go b/bcache/bcache.go index df724edaa..1176a558a 100644 --- a/bcache/bcache.go +++ b/bcache/bcache.go @@ -43,10 +43,11 @@ type BcacheStats struct { // nolint:golint // BdevStats contains statistics for one backing device. type BdevStats struct { - Name string - DirtyData uint64 - FiveMin PeriodStats - Total PeriodStats + Name string + DirtyData uint64 + FiveMin PeriodStats + Total PeriodStats + WritebackRateDebug WritebackRateDebugStats } // CacheStats contains statistics for one cache device. @@ -82,3 +83,14 @@ type PeriodStats struct { CacheMisses uint64 CacheReadaheads uint64 } + +// WritebackRateDebugStats contains bcache writeback statistics. +type WritebackRateDebugStats struct { + Rate uint64 + Dirty uint64 + Target uint64 + Proportional int64 + Integral int64 + Change int64 + NextIO int64 +} diff --git a/bcache/get.go b/bcache/get.go index 67fcf8b14..c4747ea53 100644 --- a/bcache/get.go +++ b/bcache/get.go @@ -174,6 +174,17 @@ func dehumanize(hbytes []byte) (uint64, error) { return res, nil } +func dehumanizeSigned(str string) (int64, error) { + value, err := dehumanize([]byte(strings.TrimPrefix(str, "-"))) + if err != nil { + return 0, err + } + if strings.HasPrefix(str, "-") { + return int64(-value), nil + } + return int64(value), nil +} + type parser struct { uuidPath string subDir string @@ -232,6 +243,72 @@ func parsePriorityStats(line string, ps *PriorityStats) error { return nil } +// ParseWritebackRateDebug parses lines from the writeback_rate_debug file. +func parseWritebackRateDebug(line string, wrd *WritebackRateDebugStats) error { + switch { + case strings.HasPrefix(line, "rate:"): + fields := strings.Fields(line) + rawValue := fields[len(fields)-1] + valueStr := strings.TrimSuffix(rawValue, "/sec") + value, err := dehumanize([]byte(valueStr)) + if err != nil { + return err + } + wrd.Rate = value + case strings.HasPrefix(line, "dirty:"): + fields := strings.Fields(line) + valueStr := fields[len(fields)-1] + value, err := dehumanize([]byte(valueStr)) + if err != nil { + return err + } + wrd.Dirty = value + case strings.HasPrefix(line, "target:"): + fields := strings.Fields(line) + valueStr := fields[len(fields)-1] + value, err := dehumanize([]byte(valueStr)) + if err != nil { + return err + } + wrd.Target = value + case strings.HasPrefix(line, "proportional:"): + fields := strings.Fields(line) + valueStr := fields[len(fields)-1] + value, err := dehumanizeSigned(valueStr) + if err != nil { + return err + } + wrd.Proportional = value + case strings.HasPrefix(line, "integral:"): + fields := strings.Fields(line) + valueStr := fields[len(fields)-1] + value, err := dehumanizeSigned(valueStr) + if err != nil { + return err + } + wrd.Integral = value + case strings.HasPrefix(line, "change:"): + fields := strings.Fields(line) + rawValue := fields[len(fields)-1] + valueStr := strings.TrimSuffix(rawValue, "/sec") + value, err := dehumanizeSigned(valueStr) + if err != nil { + return err + } + wrd.Change = value + case strings.HasPrefix(line, "next io:"): + fields := strings.Fields(line) + rawValue := fields[len(fields)-1] + valueStr := strings.TrimSuffix(rawValue, "ms") + value, err := strconv.ParseInt(valueStr, 10, 64) + if err != nil { + return err + } + wrd.NextIO = value + } + return nil +} + func (p *parser) getPriorityStats() PriorityStats { var res PriorityStats @@ -263,6 +340,35 @@ func (p *parser) getPriorityStats() PriorityStats { return res } +func (p *parser) getWritebackRateDebug() WritebackRateDebugStats { + var res WritebackRateDebugStats + + if p.err != nil { + return res + } + path := path.Join(p.currentDir, "writeback_rate_debug") + file, err := os.Open(path) + if err != nil { + p.err = fmt.Errorf("failed to read: %s", path) + return res + } + defer file.Close() + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + err = parseWritebackRateDebug(scanner.Text(), &res) + if err != nil { + p.err = fmt.Errorf("failed to parse: %s (%s)", path, err) + return res + } + } + if err := scanner.Err(); err != nil { + p.err = fmt.Errorf("failed to parse: %s (%s)", path, err) + return res + } + return res +} + // GetStats collects from sysfs files data tied to one bcache ID. func GetStats(uuidPath string, priorityStats bool) (*Stats, error) { var bs Stats @@ -339,6 +445,9 @@ func GetStats(uuidPath string, priorityStats bool) (*Stats, error) { par.setSubDir(bds.Name) bds.DirtyData = par.readValue("dirty_data") + wrd := par.getWritebackRateDebug() + bds.WritebackRateDebug = wrd + // dir //stats_five_minute par.setSubDir(bds.Name, "stats_five_minute") bds.FiveMin.Bypassed = par.readValue("bypassed") diff --git a/bcache/get_test.go b/bcache/get_test.go index e448797ff..22f562e99 100644 --- a/bcache/get_test.go +++ b/bcache/get_test.go @@ -158,3 +158,61 @@ func TestPriorityStats(t *testing.T) { t.Errorf("parsePriorityStats: '%s', want %d, got %d", in, want.UnusedPercent, got.UnusedPercent) } } + +func TestWritebackRateDebug(t *testing.T) { + var want = WritebackRateDebugStats{ + Rate: 1765376, + Dirty: 21789409280, + Target: 21894266880, + Proportional: -1124, + Integral: -257624, + Change: 2648, + NextIO: -150773, + } + var ( + in string + gotErr error + got WritebackRateDebugStats + ) + in = "rate: 1.7M/sec" + gotErr = parseWritebackRateDebug(in, &got) + if gotErr != nil || got.Rate != want.Rate { + t.Errorf("parsePriorityStats: '%s', want %d, got %d", in, want.Rate, got.Rate) + } + + in = "dirty: 20.3G" + gotErr = parseWritebackRateDebug(in, &got) + if gotErr != nil || got.Dirty != want.Dirty { + t.Errorf("parsePriorityStats: '%s', want %d, got %d", in, want.Dirty, got.Dirty) + } + + in = "target: 20.4G" + gotErr = parseWritebackRateDebug(in, &got) + if gotErr != nil || got.Target != want.Target { + t.Errorf("parsePriorityStats: '%s', want %d, got %d", in, want.Target, got.Target) + } + + in = "proportional: -1.1k" + gotErr = parseWritebackRateDebug(in, &got) + if gotErr != nil || got.Proportional != want.Proportional { + t.Errorf("parsePriorityStats: '%s', want %d, got %d", in, want.Proportional, got.Proportional) + } + + in = "integral: -251.6k" + gotErr = parseWritebackRateDebug(in, &got) + if gotErr != nil || got.Integral != want.Integral { + t.Errorf("parsePriorityStats: '%s', want %d, got %d", in, want.Integral, got.Integral) + } + + in = "change: 2.6k/sec" + gotErr = parseWritebackRateDebug(in, &got) + if gotErr != nil || got.Change != want.Change { + t.Errorf("parsePriorityStats: '%s', want %d, got %d", in, want.Change, got.Change) + } + + in = "next io: -150773ms" + gotErr = parseWritebackRateDebug(in, &got) + if gotErr != nil || got.NextIO != want.NextIO { + t.Errorf("parsePriorityStats: '%s', want %d, got %d", in, want.NextIO, got.NextIO) + } +} diff --git a/fixtures.ttar b/fixtures.ttar index 45a732155..0cd783632 100644 --- a/fixtures.ttar +++ b/fixtures.ttar @@ -4115,6 +4115,17 @@ Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Path: fixtures/sys/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/bdev0/writeback_rate_debug +Lines: 7 +rate: 1.1M/sec +dirty: 20.4G +target: 20.4G +proportional: 427.5k +integral: 790.0k +change: 321.5k/sec +next io: 17ms +Mode: 644 +# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Directory: fixtures/sys/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/bdev0/stats_day Mode: 755 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -