Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bcache plugin #286

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions plugins/all/all.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package all

import (
_ "github.com/influxdb/telegraf/plugins/apache"
_ "github.com/influxdb/telegraf/plugins/bcache"
_ "github.com/influxdb/telegraf/plugins/disque"
_ "github.com/influxdb/telegraf/plugins/elasticsearch"
_ "github.com/influxdb/telegraf/plugins/exec"
Expand Down
89 changes: 89 additions & 0 deletions plugins/bcache/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Telegraf plugin: bcache

Get bcache stat from stats_total directory and dirty_data file.

# Measurements

Meta:

- tags: `backing_dev=dev bcache_dev=dev`

Measurement names:

- dirty_data
- bypassed
- cache_bypass_hits
- cache_bypass_misses
- cache_hit_ratio
- cache_hits
- cache_miss_collisions
- cache_misses
- cache_readaheads

### Description

```
dirty_data
Amount of dirty data for this backing device in the cache. Continuously
updated unlike the cache set's version, but may be slightly off.
bypassed
Amount of IO (both reads and writes) that has bypassed the cache
cache_bypass_hits
cache_bypass_misses
Hits and misses for IO that is intended to skip the cache are still counted,
but broken out here.
cache_hits
cache_misses
cache_hit_ratio
Hits and misses are counted per individual IO as bcache sees them; a
partial hit is counted as a miss.
cache_miss_collisions
Counts instances where data was going to be inserted into the cache from a
cache miss, but raced with a write and data was already present (usually 0
since the synchronization for cache misses was rewritten)
cache_readaheads
Count of times readahead occurred.
```

# Example output

Using this configuration:

```
[bcache]
# Bcache sets path
# If not specified, then default is:
# bcachePath = "/sys/fs/bcache"
#
# By default, telegraf gather stats for all bcache devices
# Setting devices will restrict the stats to the specified
# bcache devices.
# bcacheDevs = ["bcache0", ...]
```

When run with:

```
./telegraf -config telegraf.conf -filter bcache -test
```

It produces:

```
* Plugin: bcache, Collection 1
> [backing_dev="md10" bcache_dev="bcache0"] bcache_dirty_data value=11639194
> [backing_dev="md10" bcache_dev="bcache0"] bcache_bypassed value=5167704440832
> [backing_dev="md10" bcache_dev="bcache0"] bcache_cache_bypass_hits value=146270986
> [backing_dev="md10" bcache_dev="bcache0"] bcache_cache_bypass_misses value=0
> [backing_dev="md10" bcache_dev="bcache0"] bcache_cache_hit_ratio value=90
> [backing_dev="md10" bcache_dev="bcache0"] bcache_cache_hits value=511941651
> [backing_dev="md10" bcache_dev="bcache0"] bcache_cache_miss_collisions value=157678
> [backing_dev="md10" bcache_dev="bcache0"] bcache_cache_misses value=50647396
> [backing_dev="md10" bcache_dev="bcache0"] bcache_cache_readaheads value=0
```
144 changes: 144 additions & 0 deletions plugins/bcache/bcache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package bcache

import (
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"

"github.com/influxdb/telegraf/plugins"
)

type Bcache struct {
BcachePath string
BcacheDevs []string
}

var sampleConfig = `
# Bcache sets path
# If not specified, then default is:
# bcachePath = "/sys/fs/bcache"
#
# By default, telegraf gather stats for all bcache devices
# Setting devices will restrict the stats to the specified
# bcache devices.
# bcacheDevs = ["bcache0", ...]
`

func (b *Bcache) SampleConfig() string {
return sampleConfig
}

func (b *Bcache) Description() string {
return "Read metrics of bcache from stats_total and dirty_data"
}

func getBackingDevs(bcachePath string) []string {
bdevs, err := filepath.Glob(bcachePath + "/*/bdev*")
if len(bdevs) < 1 {
panic("Can't found any bcache device")
}
if err != nil {
panic(err)
}
return bdevs
}

func getTags(bdev string) map[string]string {
backingDevFile, _ := os.Readlink(bdev)
backingDevPath := strings.Split(backingDevFile, "/")
backingDev := backingDevPath[len(backingDevPath)-2]

bcacheDevFile, _ := os.Readlink(bdev + "/dev")
bcacheDevPath := strings.Split(bcacheDevFile, "/")
bcacheDev := bcacheDevPath[len(bcacheDevPath)-1]

return map[string]string{"backing_dev": backingDev, "bcache_dev": bcacheDev}
}

func prettyToBytes(v string) uint64 {
var factors = map[string]uint64{
"k": 1 << 10,
"M": 1 << 20,
"G": 1 << 30,
"T": 1 << 40,
"P": 1 << 50,
"E": 1 << 60,
}
var factor uint64
factor = 1
prefix := v[len(v)-1 : len(v)]
if factors[prefix] != 0 {
v = v[:len(v)-1]
factor = factors[prefix]
}
result, _ := strconv.ParseFloat(v, 32)
result = result * float64(factor)

return uint64(result)
}

func (b *Bcache) gatherBcache(bdev string, acc plugins.Accumulator) error {
tags := getTags(bdev)
metrics, err := filepath.Glob(bdev + "/stats_total/*")
if len(metrics) < 0 {
panic("Can't read any stats file")
}
file, err := ioutil.ReadFile(bdev + "/dirty_data")
if err != nil {
panic(err)
}
rawValue := strings.TrimSpace(string(file))
value := prettyToBytes(rawValue)
acc.Add("dirty_data", value, tags)

for _, path := range metrics {
key := filepath.Base(path)
file, err := ioutil.ReadFile(path)
rawValue := strings.TrimSpace(string(file))
if err != nil {
panic(err)
}
if key == "bypassed" {
value := prettyToBytes(rawValue)
acc.Add(key, value, tags)
} else {
value, _ := strconv.ParseUint(rawValue, 10, 64)
acc.Add(key, value, tags)
}
}
return nil
}

func (b *Bcache) Gather(acc plugins.Accumulator) error {
bcacheDevsChecked := make(map[string]bool)
var restrictDevs bool
if len(b.BcacheDevs) != 0 {
restrictDevs = true
for _, bcacheDev := range b.BcacheDevs {
bcacheDevsChecked[bcacheDev] = true
}
}

bcachePath := b.BcachePath
if len(bcachePath) == 0 {
bcachePath = "/sys/fs/bcache"
}
for _, bdev := range getBackingDevs(bcachePath) {
if restrictDevs {
bcacheDev := getTags(bdev)["bcache_dev"]
if !bcacheDevsChecked[bcacheDev] {
continue
}
}
b.gatherBcache(bdev, acc)
}
return nil
}

func init() {
plugins.Add("bcache", func() plugins.Plugin {
return &Bcache{}
})
}
153 changes: 153 additions & 0 deletions plugins/bcache/bcache_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package bcache

import (
"io/ioutil"
"os"
"testing"

"github.com/influxdb/telegraf/testutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

const (
dirty_data = "1.5G"
bypassed = "4.7T"
cache_bypass_hits = "146155333"
cache_bypass_misses = "0"
cache_hit_ratio = "90"
cache_hits = "511469583"
cache_miss_collisions = "157567"
cache_misses = "50616331"
cache_readaheads = "2"
)

var (
testBcachePath = os.TempDir() + "/telegraf/sys/fs/bcache"
testBcacheUuidPath = testBcachePath + "/663955a3-765a-4737-a9fd-8250a7a78411"
testBcacheDevPath = os.TempDir() + "/telegraf/sys/devices/virtual/block/bcache0"
testBcacheBackingDevPath = os.TempDir() + "/telegraf/sys/devices/virtual/block/md10"
)

type metrics struct {
name string
value uint64
}

func TestBcacheGeneratesMetrics(t *testing.T) {
err := os.MkdirAll(testBcacheUuidPath, 0755)
require.NoError(t, err)

err = os.MkdirAll(testBcacheDevPath, 0755)
require.NoError(t, err)

err = os.MkdirAll(testBcacheBackingDevPath+"/bcache", 0755)
require.NoError(t, err)

err = os.Symlink(testBcacheBackingDevPath+"/bcache", testBcacheUuidPath+"/bdev0")
require.NoError(t, err)

err = os.Symlink(testBcacheDevPath, testBcacheUuidPath+"/bdev0/dev")
require.NoError(t, err)

err = os.MkdirAll(testBcacheUuidPath+"/bdev0/stats_total", 0755)
require.NoError(t, err)

err = ioutil.WriteFile(testBcacheUuidPath+"/bdev0/dirty_data", []byte(dirty_data), 0644)
require.NoError(t, err)

err = ioutil.WriteFile(testBcacheUuidPath+"/bdev0/stats_total/bypassed", []byte(bypassed), 0644)
require.NoError(t, err)

err = ioutil.WriteFile(testBcacheUuidPath+"/bdev0/stats_total/cache_bypass_hits", []byte(cache_bypass_hits), 0644)
require.NoError(t, err)

err = ioutil.WriteFile(testBcacheUuidPath+"/bdev0/stats_total/cache_bypass_misses", []byte(cache_bypass_misses), 0644)
require.NoError(t, err)

err = ioutil.WriteFile(testBcacheUuidPath+"/bdev0/stats_total/cache_hit_ratio", []byte(cache_hit_ratio), 0644)
require.NoError(t, err)

err = ioutil.WriteFile(testBcacheUuidPath+"/bdev0/stats_total/cache_hits", []byte(cache_hits), 0644)
require.NoError(t, err)

err = ioutil.WriteFile(testBcacheUuidPath+"/bdev0/stats_total/cache_miss_collisions", []byte(cache_miss_collisions), 0644)
require.NoError(t, err)

err = ioutil.WriteFile(testBcacheUuidPath+"/bdev0/stats_total/cache_misses", []byte(cache_misses), 0644)
require.NoError(t, err)

err = ioutil.WriteFile(testBcacheUuidPath+"/bdev0/stats_total/cache_readaheads", []byte(cache_readaheads), 0644)
require.NoError(t, err)

intMetrics := []*metrics{
{
name: "dirty_data",
value: 1610612736,
},
{
name: "bypassed",
value: 5167704440832,
},
{
name: "cache_bypass_hits",
value: 146155333,
},
{
name: "cache_bypass_misses",
value: 0,
},
{
name: "cache_hit_ratio",
value: 90,
},
{
name: "cache_hits",
value: 511469583,
},
{
name: "cache_miss_collisions",
value: 157567,
},
{
name: "cache_misses",
value: 50616331,
},
{
name: "cache_readaheads",
value: 2,
},
}

tags := map[string]string{
"backing_dev": "md10",
"bcache_dev": "bcache0",
}

var acc testutil.Accumulator

//all devs
b := &Bcache{BcachePath: testBcachePath}

err = b.Gather(&acc)
require.NoError(t, err)

for _, metric := range intMetrics {
assert.True(t, acc.HasUIntValue(metric.name), metric.name)
assert.True(t, acc.CheckTaggedValue(metric.name, metric.value, tags))
}

//one exist dev
b = &Bcache{BcachePath: testBcachePath, BcacheDevs: []string{"bcache0"}}

err = b.Gather(&acc)
require.NoError(t, err)

for _, metric := range intMetrics {
assert.True(t, acc.HasUIntValue(metric.name), metric.name)
assert.True(t, acc.CheckTaggedValue(metric.name, metric.value, tags))
}

err = os.RemoveAll(os.TempDir() + "/telegraf")
require.NoError(t, err)
}