Skip to content

Commit

Permalink
go.d add dmcache collector (netdata#17947)
Browse files Browse the repository at this point in the history
  • Loading branch information
ilyam8 committed Jun 18, 2024
1 parent 57f59bc commit bcaa5e7
Show file tree
Hide file tree
Showing 14 changed files with 925 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/go/collectors/go.d.plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ see the appropriate collector readme.
| [coredns](https://github.com/netdata/netdata/tree/master/src/go/collectors/go.d.plugin/modules/coredns) | CoreDNS |
| [couchbase](https://github.com/netdata/netdata/tree/master/src/go/collectors/go.d.plugin/modules/couchbase) | Couchbase |
| [couchdb](https://github.com/netdata/netdata/tree/master/src/go/collectors/go.d.plugin/modules/couchdb) | CouchDB |
| [dmcache](https://github.com/netdata/netdata/tree/master/src/go/collectors/go.d.plugin/modules/dmcache) | DMCache |
| [dnsdist](https://github.com/netdata/netdata/tree/master/src/go/collectors/go.d.plugin/modules/dnsdist) | Dnsdist |
| [dnsmasq](https://github.com/netdata/netdata/tree/master/src/go/collectors/go.d.plugin/modules/dnsmasq) | Dnsmasq DNS Forwarder |
| [dnsmasq_dhcp](https://github.com/netdata/netdata/tree/master/src/go/collectors/go.d.plugin/modules/dnsmasq_dhcp) | Dnsmasq DHCP |
Expand Down
1 change: 1 addition & 0 deletions src/go/collectors/go.d.plugin/config/go.d.conf
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ modules:
# coredns: yes
# couchbase: yes
# couchdb: yes
# dmcache: yes
# dnsdist: yes
# dnsmasq: yes
# dnsmasq_dhcp: yes
Expand Down
5 changes: 5 additions & 0 deletions src/go/collectors/go.d.plugin/config/go.d/dmcache.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
## All available configuration options, their descriptions and default values:
## https://github.com/netdata/netdata/tree/master/src/go/collectors/go.d.plugin/modules/dmcache#readme

jobs:
- name: dmcache
149 changes: 149 additions & 0 deletions src/go/collectors/go.d.plugin/modules/dmcache/charts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// SPDX-License-Identifier: GPL-3.0-or-later

package dmcache

import (
"fmt"
"strings"

"github.com/netdata/netdata/go/go.d.plugin/agent/module"
)

const (
prioDeviceCacheSpaceUsage = module.Priority + iota
prioDeviceMetaSpaceUsage
prioDeviceReadEfficiency
prioDeviceWriteEfficiency
prioDeviceActivity
prioDeviceDirty
)

var deviceChartsTmpl = module.Charts{
chartDeviceCacheSpaceUsageTmpl.Copy(),
chartDeviceMetadataSpaceUsageTmpl.Copy(),

chartDeviceReadEfficiencyTmpl.Copy(),
chartDeviceWriteEfficiencyTmpl.Copy(),

chartDeviceActivityTmpl.Copy(),

chartDeviceDirtySizeTmpl.Copy(),
}

var (
chartDeviceCacheSpaceUsageTmpl = module.Chart{
ID: "dmcache_device_%s_cache_space_usage",
Title: "DMCache space usage",
Units: "bytes",
Fam: "space usage",
Ctx: "dmcache.device_cache_space_usage",
Type: module.Stacked,
Priority: prioDeviceCacheSpaceUsage,
Dims: module.Dims{
{ID: "dmcache_device_%s_cache_free_bytes", Name: "free"},
{ID: "dmcache_device_%s_cache_used_bytes", Name: "used"},
},
}
chartDeviceMetadataSpaceUsageTmpl = module.Chart{
ID: "dmcache_device_%s_metadata_space_usage",
Title: "DMCache metadata space usage",
Units: "bytes",
Fam: "space usage",
Ctx: "dmcache.device_metadata_space_usage",
Type: module.Stacked,
Priority: prioDeviceMetaSpaceUsage,
Dims: module.Dims{
{ID: "dmcache_device_%s_metadata_free_bytes", Name: "free"},
{ID: "dmcache_device_%s_metadata_used_bytes", Name: "used"},
},
}
)

var (
chartDeviceReadEfficiencyTmpl = module.Chart{
ID: "dmcache_device_%s_read_efficiency",
Title: "DMCache read efficiency",
Units: "requests/s",
Fam: "efficiency",
Ctx: "dmcache.device_cache_read_efficiency",
Type: module.Stacked,
Priority: prioDeviceReadEfficiency,
Dims: module.Dims{
{ID: "dmcache_device_%s_read_hits", Name: "hits", Algo: module.Incremental},
{ID: "dmcache_device_%s_read_misses", Name: "misses", Algo: module.Incremental},
},
}
chartDeviceWriteEfficiencyTmpl = module.Chart{
ID: "dmcache_device_%s_write_efficiency",
Title: "DMCache write efficiency",
Units: "requests/s",
Fam: "efficiency",
Ctx: "dmcache.device_cache_write_efficiency",
Type: module.Stacked,
Priority: prioDeviceWriteEfficiency,
Dims: module.Dims{
{ID: "dmcache_device_%s_write_hits", Name: "hits", Algo: module.Incremental},
{ID: "dmcache_device_%s_write_misses", Name: "misses", Algo: module.Incremental},
},
}
)

var chartDeviceActivityTmpl = module.Chart{
ID: "dmcache_device_%s_activity",
Title: "DMCache activity",
Units: "bytes/s",
Fam: "activity",
Ctx: "dmcache.device_cache_activity",
Type: module.Area,
Priority: prioDeviceActivity,
Dims: module.Dims{
{ID: "dmcache_device_%s_promotions_bytes", Name: "promotions", Algo: module.Incremental},
{ID: "dmcache_device_%s_demotions_bytes", Name: "demotions", Mul: -1, Algo: module.Incremental},
},
}

var chartDeviceDirtySizeTmpl = module.Chart{
ID: "dmcache_device_%s_dirty_size",
Title: "DMCache dirty data size",
Units: "bytes",
Fam: "dirty size",
Ctx: "dmcache.device_cache_dirty_size",
Type: module.Area,
Priority: prioDeviceDirty,
Dims: module.Dims{
{ID: "dmcache_device_%s_dirty_bytes", Name: "dirty"},
},
}

func (c *DmCache) addDeviceCharts(device string) {
charts := deviceChartsTmpl.Copy()

for _, chart := range *charts {
chart.ID = fmt.Sprintf(chart.ID, cleanDeviceName(device))
chart.Labels = []module.Label{
{Key: "device", Value: device},
}
for _, dim := range chart.Dims {
dim.ID = fmt.Sprintf(dim.ID, device)
}
}

if err := c.Charts().Add(*charts...); err != nil {
c.Warning(err)
}
}

func (c *DmCache) removeDeviceCharts(device string) {
px := fmt.Sprintf("dmcache_device_%s_", cleanDeviceName(device))

for _, chart := range *c.Charts() {
if strings.HasPrefix(chart.ID, px) {
chart.MarkRemove()
chart.MarkNotCreated()
}
}
}

func cleanDeviceName(device string) string {
return strings.ReplaceAll(device, ".", "_")
}
173 changes: 173 additions & 0 deletions src/go/collectors/go.d.plugin/modules/dmcache/collect.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
// SPDX-License-Identifier: GPL-3.0-or-later

package dmcache

import (
"bufio"
"bytes"
"errors"
"fmt"
"strconv"
"strings"
)

type dmCacheDevice struct {
name string
metaBlockSizeSectors int64
metaUsedBlocks int64
metaTotalBlocks int64
cacheBlockSizeSectors int64
cacheUsedBlocks int64
cacheTotalBlocks int64
readHits int64
readMisses int64
writeHits int64
writeMisses int64
demotionsBlocks int64
promotionsBlocks int64
dirtyBlocks int64
}

func (c *DmCache) collect() (map[string]int64, error) {
bs, err := c.exec.cacheStatus()
if err != nil {
return nil, err
}

mx := make(map[string]int64)

if err := c.collectCacheStatus(mx, bs); err != nil {
return nil, err
}

return mx, nil
}

func (c *DmCache) collectCacheStatus(mx map[string]int64, data []byte) error {
var devices []*dmCacheDevice

sc := bufio.NewScanner(bytes.NewReader(data))

for sc.Scan() {
line := strings.TrimSpace(sc.Text())
if line == "" {
continue
}

dev, err := parseDmsetupStatusLine(line)
if err != nil {
return fmt.Errorf("malformed dmsetup status line: %v ('%s')", err, line)
}

devices = append(devices, dev)
}

seen := make(map[string]bool)

for _, dev := range devices {
seen[dev.name] = true

if !c.devices[dev.name] {
c.devices[dev.name] = true
c.addDeviceCharts(dev.name)
}

px := fmt.Sprintf("dmcache_device_%s_", dev.name)

const sectorSize = 512
metaMul := dev.metaBlockSizeSectors * sectorSize
cacheMul := dev.cacheBlockSizeSectors * sectorSize

mx[px+"metadata_free_bytes"] = (dev.metaTotalBlocks - dev.metaUsedBlocks) * metaMul
mx[px+"metadata_used_bytes"] = dev.metaUsedBlocks * metaMul
mx[px+"cache_free_bytes"] = (dev.cacheTotalBlocks - dev.cacheUsedBlocks) * cacheMul
mx[px+"cache_used_bytes"] = dev.cacheUsedBlocks * cacheMul
mx[px+"read_hits"] = dev.readHits
mx[px+"read_misses"] = dev.readMisses
mx[px+"write_hits"] = dev.writeHits
mx[px+"write_misses"] = dev.writeMisses
mx[px+"demotions_bytes"] = dev.demotionsBlocks * cacheMul
mx[px+"promotions_bytes"] = dev.promotionsBlocks * cacheMul
mx[px+"dirty_bytes"] = dev.dirtyBlocks * cacheMul
}

for dev := range c.devices {
if !seen[dev] {
delete(c.devices, dev)
c.removeDeviceCharts(dev)
}
}

if len(devices) == 0 {
return errors.New("no dm-cache devices found")
}

return nil
}

func parseDmsetupStatusLine(line string) (*dmCacheDevice, error) {
// https://www.kernel.org/doc/html/next/admin-guide/device-mapper/cache.html#status

parts := strings.Fields(line)
if len(parts) < 15 {
return nil, fmt.Errorf("want at least 15 fields, got %d", len(parts))
}

var dev dmCacheDevice
var err error

for i, s := range parts {
switch i {
case 0:
dev.name = strings.TrimSuffix(parts[0], ":")
case 4:
dev.metaBlockSizeSectors, err = parseInt(s)
case 5:
dev.metaUsedBlocks, dev.metaTotalBlocks, err = parseUsedTotalBlocks(s)
case 6:
dev.cacheBlockSizeSectors, err = parseInt(s)
case 7:
dev.cacheUsedBlocks, dev.cacheTotalBlocks, err = parseUsedTotalBlocks(s)
case 8:
dev.readHits, err = parseInt(s)
case 9:
dev.readMisses, err = parseInt(s)
case 10:
dev.writeHits, err = parseInt(s)
case 11:
dev.writeMisses, err = parseInt(s)
case 12:
dev.demotionsBlocks, err = parseInt(s)
case 13:
dev.promotionsBlocks, err = parseInt(s)
case 14:
dev.dirtyBlocks, err = parseInt(s)
}

if err != nil {
return nil, fmt.Errorf("failed to parse %d field '%s': %v", i, s, err)
}
}

return &dev, nil
}

func parseUsedTotalBlocks(info string) (int64, int64, error) {
parts := strings.Split(info, "/")
if len(parts) != 2 {
return 0, 0, errors.New("expected used/total")
}
used, err := parseInt(parts[0])
if err != nil {
return 0, 0, err
}
total, err := parseInt(parts[1])
if err != nil {
return 0, 0, err
}
return used, total, nil
}

func parseInt(s string) (int64, error) {
return strconv.ParseInt(s, 10, 64)
}
35 changes: 35 additions & 0 deletions src/go/collectors/go.d.plugin/modules/dmcache/config_schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"jsonSchema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "DMCache collector configuration.",
"type": "object",
"properties": {
"update_every": {
"title": "Update every",
"description": "Data collection interval, measured in seconds.",
"type": "integer",
"minimum": 1,
"default": 10
},
"timeout": {
"title": "Timeout",
"description": "Timeout for executing the binary, specified in seconds.",
"type": "number",
"minimum": 0.5,
"default": 2
}
},
"additionalProperties": false,
"patternProperties": {
"^name$": {}
}
},
"uiSchema": {
"uiOptions": {
"fullPage": true
},
"timeout": {
"ui:help": "Accepts decimals for precise control (e.g., type 1.5 for 1.5 seconds)."
}
}
}
Loading

0 comments on commit bcaa5e7

Please sign in to comment.